jason-rails 0.4.0 → 0.6.1
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/.ruby-version +1 -0
- data/Gemfile.lock +152 -2
- data/README.md +117 -5
- data/app/controllers/jason/api/pusher_controller.rb +15 -0
- data/app/controllers/jason/api_controller.rb +44 -2
- data/client/lib/JasonContext.d.ts +6 -1
- data/client/lib/JasonProvider.d.ts +2 -2
- data/client/lib/JasonProvider.js +5 -124
- data/client/lib/createJasonReducers.js +48 -3
- data/client/lib/createOptDis.js +0 -2
- data/client/lib/createPayloadHandler.d.ts +9 -1
- data/client/lib/createPayloadHandler.js +47 -55
- data/client/lib/createServerActionQueue.d.ts +10 -0
- data/client/lib/createServerActionQueue.js +48 -0
- data/client/lib/createServerActionQueue.test.d.ts +1 -0
- data/client/lib/createServerActionQueue.test.js +37 -0
- data/client/lib/createTransportAdapter.d.ts +5 -0
- data/client/lib/createTransportAdapter.js +20 -0
- data/client/lib/index.d.ts +3 -2
- data/client/lib/pruneIdsMiddleware.d.ts +2 -0
- data/client/lib/pruneIdsMiddleware.js +24 -0
- data/client/lib/restClient.d.ts +2 -0
- data/client/lib/restClient.js +17 -0
- data/client/lib/transportAdapters/actionCableAdapter.d.ts +5 -0
- data/client/lib/transportAdapters/actionCableAdapter.js +35 -0
- data/client/lib/transportAdapters/pusherAdapter.d.ts +5 -0
- data/client/lib/transportAdapters/pusherAdapter.js +68 -0
- data/client/lib/useJason.d.ts +5 -0
- data/client/lib/useJason.js +94 -0
- data/client/lib/useJason.test.d.ts +1 -0
- data/client/lib/useJason.test.js +85 -0
- data/client/lib/useSub.d.ts +1 -1
- data/client/lib/useSub.js +6 -3
- data/client/package.json +5 -3
- data/client/src/JasonProvider.tsx +5 -123
- data/client/src/createJasonReducers.ts +56 -3
- data/client/src/createOptDis.ts +0 -2
- data/client/src/createPayloadHandler.ts +53 -64
- data/client/src/createServerActionQueue.test.ts +42 -0
- data/client/src/createServerActionQueue.ts +47 -0
- data/client/src/createTransportAdapter.ts +13 -0
- data/client/src/pruneIdsMiddleware.ts +24 -0
- data/client/src/restClient.ts +14 -0
- data/client/src/transportAdapters/actionCableAdapter.ts +38 -0
- data/client/src/transportAdapters/pusherAdapter.ts +72 -0
- data/client/src/useJason.test.ts +87 -0
- data/client/src/useJason.ts +110 -0
- data/client/src/useSub.ts +6 -3
- data/client/yarn.lock +71 -3
- data/config/routes.rb +5 -1
- data/jason-rails.gemspec +4 -0
- data/lib/jason.rb +61 -1
- data/lib/jason/api_model.rb +2 -12
- data/lib/jason/broadcaster.rb +19 -0
- data/lib/jason/channel.rb +50 -21
- data/lib/jason/graph_helper.rb +165 -0
- data/lib/jason/includes_helper.rb +108 -0
- data/lib/jason/lua_generator.rb +71 -0
- data/lib/jason/publisher.rb +82 -37
- data/lib/jason/publisher_old.rb +112 -0
- data/lib/jason/subscription.rb +349 -97
- data/lib/jason/subscription_old.rb +171 -0
- data/lib/jason/version.rb +1 -1
- metadata +80 -11
- data/app/assets/config/jason_engine_manifest.js +0 -1
- data/app/assets/images/jason/engine/.keep +0 -0
- data/app/assets/stylesheets/jason/engine/application.css +0 -15
- data/app/helpers/jason/engine/application_helper.rb +0 -6
- data/app/jobs/jason/engine/application_job.rb +0 -6
- data/app/mailers/jason/engine/application_mailer.rb +0 -8
- data/app/models/jason/engine/application_record.rb +0 -7
- data/app/views/layouts/jason/engine/application.html.erb +0 -15
data/config/routes.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
Jason::Engine.routes.draw do
|
2
|
-
get '/api/
|
2
|
+
get '/api/config', to: 'api#configuration'
|
3
3
|
post '/api/action', to: 'api#action'
|
4
|
+
post '/api/create_subscription', to: 'api#create_subscription'
|
5
|
+
post '/api/remove_subscription', to: 'api#remove_subscription'
|
6
|
+
post '/api/get_payload', to: 'api#get_payload'
|
7
|
+
post '/api/pusher/auth', to: 'api/pusher#auth'
|
4
8
|
end
|
data/jason-rails.gemspec
CHANGED
@@ -27,4 +27,8 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_dependency "connection_pool", ">= 2.2.3"
|
28
28
|
spec.add_dependency "redis", ">= 4"
|
29
29
|
spec.add_dependency "jsondiff"
|
30
|
+
|
31
|
+
spec.add_development_dependency "rspec-rails"
|
32
|
+
spec.add_development_dependency "sqlite3"
|
33
|
+
spec.add_development_dependency "pry"
|
30
34
|
end
|
data/lib/jason.rb
CHANGED
@@ -7,10 +7,70 @@ require 'jason/api_model'
|
|
7
7
|
require 'jason/channel'
|
8
8
|
require 'jason/publisher'
|
9
9
|
require 'jason/subscription'
|
10
|
+
require 'jason/broadcaster'
|
10
11
|
require 'jason/engine'
|
12
|
+
require 'jason/lua_generator'
|
13
|
+
require 'jason/includes_helper'
|
14
|
+
require 'jason/graph_helper'
|
11
15
|
|
12
16
|
module Jason
|
13
17
|
class Error < StandardError; end
|
14
18
|
|
15
|
-
|
19
|
+
self.mattr_accessor :schema
|
20
|
+
self.mattr_accessor :transport_service
|
21
|
+
self.mattr_accessor :redis
|
22
|
+
self.mattr_accessor :pusher
|
23
|
+
self.mattr_accessor :pusher_key
|
24
|
+
self.mattr_accessor :pusher_region
|
25
|
+
self.mattr_accessor :pusher_channel_prefix
|
26
|
+
self.mattr_accessor :authorization_service
|
27
|
+
|
28
|
+
self.schema = {}
|
29
|
+
self.transport_service = :action_cable
|
30
|
+
self.pusher_region = 'eu'
|
31
|
+
self.pusher_channel_prefix = 'jason'
|
32
|
+
|
33
|
+
def self.init
|
34
|
+
# Check if the schema has changed since last time app was started. If so, do some work to ensure cache contains the correct data
|
35
|
+
got_lock = $redis_jason.set('jason:schema:lock', nx: true, ex: 3600) # Basic lock mechanism for multi-process environments
|
36
|
+
return if !got_lock
|
37
|
+
|
38
|
+
previous_schema = JSON.parse($redis_jason.get('jason:last_schema') || '{}')
|
39
|
+
current_schema = Jason.schema.deep_stringify_keys.deep_transform_values { |v| v.is_a?(Symbol) ? v.to_s : v }
|
40
|
+
pp current_schema
|
41
|
+
current_schema.each do |model, config|
|
42
|
+
if config != previous_schema[model]
|
43
|
+
puts "Config changed for #{model}"
|
44
|
+
puts "Old config was #{previous_schema[model]}"
|
45
|
+
puts "New config is #{config}"
|
46
|
+
puts "Rebuilding cache for #{model}"
|
47
|
+
model.classify.constantize.cache_all
|
48
|
+
puts "Done"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
$redis_jason.set('jason:last_schema', current_schema.to_json)
|
53
|
+
ensure
|
54
|
+
$redis_jason.del('jason:schema:lock')
|
55
|
+
|
56
|
+
previous_config = 'test'
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# this function maps the vars from your app into your engine
|
61
|
+
def self.setup(&block)
|
62
|
+
yield self
|
63
|
+
|
64
|
+
$redis_jason = self.redis || ::ConnectionPool::Wrapper.new(size: 5, timeout: 3) { ::Redis.new(url: ENV['REDIS_URL']) }
|
65
|
+
|
66
|
+
if ![:action_cable, :pusher].include?(self.transport_service)
|
67
|
+
raise "Unknown transport service '#{self.transport_service}' specified"
|
68
|
+
end
|
69
|
+
|
70
|
+
if self.transport_service == :pusher && self.pusher.blank?
|
71
|
+
raise "Pusher specified as transport service but no Pusher client provided. Please configure with config.pusher = Pusher::Client.new(...)"
|
72
|
+
end
|
73
|
+
|
74
|
+
init
|
75
|
+
end
|
16
76
|
end
|
data/lib/jason/api_model.rb
CHANGED
@@ -8,7 +8,7 @@ class Jason::ApiModel
|
|
8
8
|
|
9
9
|
def initialize(name)
|
10
10
|
@name = name
|
11
|
-
@model = OpenStruct.new(
|
11
|
+
@model = OpenStruct.new(Jason.schema[name.to_sym])
|
12
12
|
end
|
13
13
|
|
14
14
|
def allowed_params
|
@@ -19,10 +19,6 @@ class Jason::ApiModel
|
|
19
19
|
model.allowed_object_params || []
|
20
20
|
end
|
21
21
|
|
22
|
-
def include_models
|
23
|
-
model.include_models || []
|
24
|
-
end
|
25
|
-
|
26
22
|
def include_methods
|
27
23
|
model.include_methods || []
|
28
24
|
end
|
@@ -52,12 +48,6 @@ class Jason::ApiModel
|
|
52
48
|
end
|
53
49
|
|
54
50
|
def as_json_config
|
55
|
-
|
56
|
-
reflection = name.classify.constantize.reflect_on_association(assoc.to_sym)
|
57
|
-
api_model = Jason::ApiModel.new(reflection.klass.name.underscore)
|
58
|
-
{ assoc => { only: api_model.subscribed_fields, methods: api_model.include_methods } }
|
59
|
-
end
|
60
|
-
|
61
|
-
{ only: subscribed_fields, include: include_configs, methods: include_methods }
|
51
|
+
{ only: subscribed_fields, methods: include_methods }
|
62
52
|
end
|
63
53
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Jason::Broadcaster
|
2
|
+
attr_reader :channel
|
3
|
+
|
4
|
+
def initialize(channel)
|
5
|
+
@channel = channel
|
6
|
+
end
|
7
|
+
|
8
|
+
def pusher_channel_name
|
9
|
+
"private-#{Jason.pusher_channel_prefix}-#{channel}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def broadcast(message)
|
13
|
+
if Jason.transport_service == :action_cable
|
14
|
+
ActionCable.server.broadcast(channel, message)
|
15
|
+
elsif Jason.transport_service == :pusher
|
16
|
+
Jason.pusher.trigger(pusher_channel_name, 'changed', message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/jason/channel.rb
CHANGED
@@ -1,32 +1,27 @@
|
|
1
1
|
class Jason::Channel < ActionCable::Channel::Base
|
2
2
|
attr_accessor :subscriptions
|
3
3
|
|
4
|
+
def subscribe
|
5
|
+
stream_from 'jason'
|
6
|
+
end
|
7
|
+
|
4
8
|
def receive(message)
|
5
|
-
|
9
|
+
handle_message(message)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def handle_message(message)
|
15
|
+
pp message['createSubscription']
|
16
|
+
@subscriptions ||= []
|
6
17
|
|
7
18
|
begin # ActionCable swallows errors in this message - ensure they're output to logs.
|
8
19
|
if (config = message['createSubscription'])
|
9
|
-
|
10
|
-
subscriptions.push(subscription)
|
11
|
-
subscription.add_consumer(identifier)
|
12
|
-
config.keys.each do |model|
|
13
|
-
transmit(subscription.get(model.to_s.underscore))
|
14
|
-
end
|
15
|
-
stream_from subscription.channel
|
20
|
+
create_subscription(config['model'], config['conditions'], config['includes'])
|
16
21
|
elsif (config = message['removeSubscription'])
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# Rails for some reason removed stop_stream_from, so we need to stop all and then restart the other streams
|
22
|
-
# stop_all_streams
|
23
|
-
# subscriptions.each do |s|
|
24
|
-
# stream_from s.channel
|
25
|
-
# end
|
26
|
-
elsif (data = message['getPayload'])
|
27
|
-
config = data['config']
|
28
|
-
model = data['model']
|
29
|
-
Jason::Subscription.new(config: config).get(model.to_s.underscore)
|
22
|
+
remove_subscription(config)
|
23
|
+
elsif (config = message['getPayload'])
|
24
|
+
get_payload(config, message['forceRefresh'])
|
30
25
|
end
|
31
26
|
rescue => e
|
32
27
|
puts e.message
|
@@ -34,4 +29,38 @@ class Jason::Channel < ActionCable::Channel::Base
|
|
34
29
|
raise e
|
35
30
|
end
|
36
31
|
end
|
32
|
+
|
33
|
+
def create_subscription(model, conditions, includes)
|
34
|
+
subscription = Jason::Subscription.upsert_by_config(model, conditions: conditions || {}, includes: includes || nil)
|
35
|
+
|
36
|
+
return if !subscription.user_can_access?(current_user)
|
37
|
+
stream_from subscription.channel
|
38
|
+
|
39
|
+
subscriptions.push(subscription)
|
40
|
+
subscription.add_consumer(identifier)
|
41
|
+
subscription.get.each do |payload|
|
42
|
+
pp payload
|
43
|
+
transmit(payload) if payload.present?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def remove_subscription(config)
|
48
|
+
subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
|
49
|
+
subscriptions.reject! { |s| s.id == subscription.id }
|
50
|
+
subscription.remove_consumer(identifier)
|
51
|
+
|
52
|
+
# TODO Stop streams
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_payload(config, force_refresh = false)
|
56
|
+
subscription = Jason::Subscription.upsert_by_config(config['model'], conditions: config['conditions'], includes: config['includes'])
|
57
|
+
|
58
|
+
return if !subscription.user_can_access?(current_user)
|
59
|
+
if force_refresh
|
60
|
+
subscription.set_ids_for_sub_models
|
61
|
+
end
|
62
|
+
subscription.get.each do |model_name, payload|
|
63
|
+
transmit(payload) if payload.present?
|
64
|
+
end
|
65
|
+
end
|
37
66
|
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
class Jason::GraphHelper
|
2
|
+
attr_reader :id, :includes_helper
|
3
|
+
|
4
|
+
def initialize(id, includes_helper)
|
5
|
+
@id = id
|
6
|
+
@includes_helper = includes_helper
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_edge(parent_model, parent_id, child_model, child_id)
|
10
|
+
edge = "#{parent_model}:#{parent_id}/#{child_model}:#{child_id}"
|
11
|
+
$redis_jason.sadd("jason:subscriptions:#{id}:graph", edge)
|
12
|
+
end
|
13
|
+
|
14
|
+
def remove_edge(parent_model, parent_id, child_model, child_id)
|
15
|
+
edge = "#{parent_model}:#{parent_id}/#{child_model}:#{child_id}"
|
16
|
+
$redis_jason.srem("jason:subscriptions:#{id}:graph", edge)
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_edges(all_models, instance_ids)
|
20
|
+
edges = build_edges(all_models, instance_ids)
|
21
|
+
$redis_jason.sadd("jason:subscriptions:#{id}:graph", edges)
|
22
|
+
end
|
23
|
+
|
24
|
+
def remove_edges(all_models, instance_ids)
|
25
|
+
edges = build_edges(all_models, instance_ids)
|
26
|
+
$redis_jason.srem("jason:subscriptions:#{id}:graph", edges)
|
27
|
+
end
|
28
|
+
|
29
|
+
def apply_remove_node(node)
|
30
|
+
edges = $redis_jason.smembers("jason:subscriptions:#{id}:graph")
|
31
|
+
edges = find_edges_with_node(edges, node)
|
32
|
+
diff_edges_from_graph(remove_edges: edges)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add and remove edges, return graph before and after
|
36
|
+
def apply_update(add: nil, remove: nil)
|
37
|
+
add_edges = []
|
38
|
+
remove_edges = []
|
39
|
+
|
40
|
+
if add.present?
|
41
|
+
add.each do |edge_set|
|
42
|
+
add_edges += build_edges(edge_set[:model_names], edge_set[:instance_ids])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
if remove.present?
|
47
|
+
remove.each do |edge_set|
|
48
|
+
remove_edges += build_edges(edge_set[:model_names], edge_set[:instance_ids], include_root: false)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
diff_edges_from_graph(add_edges: add_edges, remove_edges: remove_edges)
|
52
|
+
end
|
53
|
+
|
54
|
+
def diff_edges_from_graph(add_edges: [], remove_edges: [])
|
55
|
+
old_edges, new_edges = Jason::LuaGenerator.new.update_set_with_diff("jason:subscriptions:#{id}:graph", add_edges.flatten, remove_edges.flatten)
|
56
|
+
|
57
|
+
old_graph = build_graph_from_edges(old_edges)
|
58
|
+
new_graph = build_graph_from_edges(new_edges)
|
59
|
+
|
60
|
+
old_nodes = (old_graph.values + old_graph.keys).flatten.uniq - ['root']
|
61
|
+
new_nodes = (new_graph.values + new_graph.keys).flatten.uniq - ['root']
|
62
|
+
orphan_nodes = find_orphans_in_graph(new_graph)
|
63
|
+
|
64
|
+
added_nodes = new_nodes - old_nodes - orphan_nodes
|
65
|
+
removed_nodes = old_nodes - new_nodes + orphan_nodes
|
66
|
+
|
67
|
+
orphaned_edges = orphan_nodes.map do |node|
|
68
|
+
find_edges_with_node(new_edges, node)
|
69
|
+
end.flatten
|
70
|
+
|
71
|
+
if orphaned_edges.present?
|
72
|
+
$redis_jason.srem("jason:subscriptions:#{id}:graph", orphaned_edges)
|
73
|
+
end
|
74
|
+
|
75
|
+
ids_to_add = {}
|
76
|
+
ids_to_remove = {}
|
77
|
+
|
78
|
+
added_nodes.each do |node|
|
79
|
+
model_name, instance_id = node.split(':')
|
80
|
+
ids_to_add[model_name] ||= []
|
81
|
+
ids_to_add[model_name].push(instance_id)
|
82
|
+
end
|
83
|
+
|
84
|
+
removed_nodes.each do |node|
|
85
|
+
model_name, instance_id = node.split(':')
|
86
|
+
ids_to_remove[model_name] ||= []
|
87
|
+
ids_to_remove[model_name].push(instance_id)
|
88
|
+
end
|
89
|
+
|
90
|
+
{ ids_to_remove: ids_to_remove, ids_to_add: ids_to_add }
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_edges_with_node(edges, node)
|
94
|
+
edges.select do |edge|
|
95
|
+
parent, child = edge.split('/')
|
96
|
+
parent == node || child == node
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def find_orphans
|
101
|
+
edges = $redis_jason.smembers("jason:subscriptions:#{id}:graph")
|
102
|
+
graph = build_graph_from_edges(edges)
|
103
|
+
find_orphans_in_graph(graph)
|
104
|
+
end
|
105
|
+
|
106
|
+
def find_orphans_in_graph(graph)
|
107
|
+
reachable_nodes = get_reachable_nodes(graph)
|
108
|
+
all_nodes = (graph.values + graph.keys).flatten.uniq - ['root']
|
109
|
+
all_nodes - reachable_nodes
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_reachable_nodes(graph, parent = 'root')
|
113
|
+
reached_nodes = graph[parent] || []
|
114
|
+
reached_nodes.each do |child|
|
115
|
+
reached_nodes += get_reachable_nodes(graph, child)
|
116
|
+
end
|
117
|
+
reached_nodes
|
118
|
+
end
|
119
|
+
|
120
|
+
def build_graph_from_edges(edges)
|
121
|
+
graph = {}
|
122
|
+
edges.each do |edge|
|
123
|
+
parent, child = edge.split('/')
|
124
|
+
graph[parent] ||= []
|
125
|
+
graph[parent].push(child)
|
126
|
+
end
|
127
|
+
graph
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def build_edges(all_models, instance_ids, include_root: true)
|
133
|
+
# Build the tree
|
134
|
+
# Find parent -> child model relationships
|
135
|
+
edges = []
|
136
|
+
|
137
|
+
all_models.each_with_index do |parent_model, parent_idx|
|
138
|
+
all_models.each_with_index do |child_model, child_idx|
|
139
|
+
next if parent_model == child_model
|
140
|
+
next if !includes_helper.in_sub(parent_model, child_model)
|
141
|
+
|
142
|
+
pairs = instance_ids.map { |row| [row[parent_idx], row[child_idx]] }
|
143
|
+
.uniq
|
144
|
+
.reject{ |pair| pair[0].blank? || pair[1].blank? }
|
145
|
+
|
146
|
+
edges += pairs.map.each do |pair|
|
147
|
+
"#{parent_model}:#{pair[0]}/#{child_model}:#{pair[1]}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
root_model = includes_helper.root_model
|
153
|
+
|
154
|
+
if include_root && all_models.include?(root_model)
|
155
|
+
root_idx = all_models.find_index(root_model)
|
156
|
+
root_ids = instance_ids.map { |row| row[root_idx] }.uniq.compact
|
157
|
+
|
158
|
+
edges += root_ids.map do |id|
|
159
|
+
"root/#{root_model}:#{id}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
edges
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Helper to provide other modules with information about the includes of a subscription
|
2
|
+
|
3
|
+
class Jason::IncludesHelper
|
4
|
+
attr_accessor :main_tree
|
5
|
+
|
6
|
+
def initialize(main_tree)
|
7
|
+
raise "Root must be hash" if !main_tree.is_a?(Hash)
|
8
|
+
raise "Only one root key allowed" if main_tree.keys.size != 1
|
9
|
+
@main_tree = main_tree
|
10
|
+
end
|
11
|
+
|
12
|
+
def all_models_recursive(tree)
|
13
|
+
sub_models = if tree.is_a?(Hash)
|
14
|
+
tree.map do |k,v|
|
15
|
+
[k, all_models_recursive(v)]
|
16
|
+
end
|
17
|
+
elsif tree.is_a?(Array)
|
18
|
+
tree.map do |v|
|
19
|
+
all_models_recursive(v)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
tree
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def all_models(model_name = nil)
|
27
|
+
model_name = model_name.presence || root_model
|
28
|
+
assoc_name = get_assoc_name(model_name)
|
29
|
+
tree = get_tree_for(assoc_name)
|
30
|
+
[model_name, all_models_recursive(tree)].flatten.uniq.map(&:to_s).map(&:singularize)
|
31
|
+
end
|
32
|
+
|
33
|
+
def root_model
|
34
|
+
main_tree.keys[0]
|
35
|
+
end
|
36
|
+
|
37
|
+
# assoc could be plural or not, so need to scan both.
|
38
|
+
def get_assoc_name(model_name, haystack = main_tree)
|
39
|
+
if haystack.is_a?(Hash)
|
40
|
+
haystack.each do |assoc_name, includes_tree|
|
41
|
+
if model_name.pluralize == assoc_name.to_s.pluralize
|
42
|
+
return assoc_name
|
43
|
+
else
|
44
|
+
found_assoc = get_assoc_name(model_name, includes_tree)
|
45
|
+
return found_assoc if found_assoc
|
46
|
+
end
|
47
|
+
end
|
48
|
+
elsif haystack.is_a?(Array)
|
49
|
+
haystack.each do |element|
|
50
|
+
if element.is_a?(String)
|
51
|
+
if model_name.pluralize == element.pluralize
|
52
|
+
return element
|
53
|
+
end
|
54
|
+
else
|
55
|
+
found_assoc = get_assoc_name(model_name, element)
|
56
|
+
return found_assoc if found_assoc
|
57
|
+
end
|
58
|
+
end
|
59
|
+
else
|
60
|
+
if model_name.pluralize == haystack.to_s.pluralize
|
61
|
+
return haystack
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_tree_for(needle, assoc_name = nil, haystack = main_tree)
|
69
|
+
return haystack if needle.to_s.pluralize == assoc_name.to_s.pluralize
|
70
|
+
|
71
|
+
if haystack.is_a?(Hash)
|
72
|
+
haystack.each do |assoc_name, includes_tree|
|
73
|
+
found_haystack = get_tree_for(needle, assoc_name, includes_tree)
|
74
|
+
return found_haystack if found_haystack.present?
|
75
|
+
end
|
76
|
+
elsif haystack.is_a?(Array)
|
77
|
+
haystack.each do |includes_tree|
|
78
|
+
found_haystack = get_tree_for(needle, nil, includes_tree)
|
79
|
+
return found_haystack if found_haystack.present?
|
80
|
+
end
|
81
|
+
elsif haystack.is_a?(String)
|
82
|
+
found_haystack = get_tree_for(needle, haystack, nil)
|
83
|
+
return found_haystack if found_haystack.present?
|
84
|
+
end
|
85
|
+
|
86
|
+
return []
|
87
|
+
end
|
88
|
+
|
89
|
+
def in_sub(parent_model, child_model)
|
90
|
+
tree = get_tree_for(parent_model)
|
91
|
+
|
92
|
+
if tree.is_a?(Hash)
|
93
|
+
return tree.keys.map(&:singularize).include?(child_model)
|
94
|
+
elsif tree.is_a?(Array)
|
95
|
+
tree.each do |element|
|
96
|
+
if element.is_a?(String)
|
97
|
+
return true if element.singularize == child_model
|
98
|
+
elsif element.is_a?(Hash)
|
99
|
+
return true if element.keys.map(&:singularize).include?(child_model)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
elsif tree.is_a?(String)
|
103
|
+
return tree.singularize == child_model
|
104
|
+
end
|
105
|
+
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
end
|