jason-rails 0.4.1 → 0.6.2
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/JasonContext.js +4 -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 +5 -2
- data/client/lib/index.js +3 -1
- data/client/lib/makeEager.js +2 -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/JasonContext.ts +4 -1
- 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/index.ts +3 -1
- data/client/src/makeEager.ts +2 -2
- 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 -3
@@ -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
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Jason::LuaGenerator
|
2
|
+
## TODO load these scripts and evalsha
|
3
|
+
def cache_json(model_name, id, payload)
|
4
|
+
cmd = <<~LUA
|
5
|
+
local gidx = redis.call('INCR', 'jason:gidx')
|
6
|
+
redis.call( 'set', 'jason:cache:' .. ARGV[1] .. ':' .. ARGV[2] .. ':gidx', gidx )
|
7
|
+
redis.call( 'hset', 'jason:cache:' .. ARGV[1], ARGV[2], ARGV[3] )
|
8
|
+
return gidx
|
9
|
+
LUA
|
10
|
+
|
11
|
+
result = $redis_jason.eval cmd, [], [model_name, id, payload.to_json]
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_payload(model_name, sub_id)
|
15
|
+
# If value has changed, return old value and new idx. Otherwise do nothing.
|
16
|
+
cmd = <<~LUA
|
17
|
+
local t = {}
|
18
|
+
local models = {}
|
19
|
+
local ids = redis.call('smembers', 'jason:subscriptions:' .. ARGV[2] .. ':ids:' .. ARGV[1])
|
20
|
+
|
21
|
+
for k,id in pairs(ids) do
|
22
|
+
models[#models+1] = redis.call( 'hget', 'jason:cache:' .. ARGV[1], id)
|
23
|
+
end
|
24
|
+
|
25
|
+
t[#t+1] = models
|
26
|
+
t[#t+1] = redis.call( 'get', 'jason:subscription:' .. ARGV[2] .. ':' .. ARGV[1] .. ':idx' )
|
27
|
+
|
28
|
+
return t
|
29
|
+
LUA
|
30
|
+
|
31
|
+
$redis_jason.eval cmd, [], [model_name, sub_id]
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_subscription(model_name, id, sub_id, gidx)
|
35
|
+
# If value has changed, return old value and new idx. Otherwise do nothing.
|
36
|
+
cmd = <<~LUA
|
37
|
+
local last_gidx = redis.call('get', 'jason:cache:' .. ARGV[1] .. ':' .. ARGV[2] .. ':gidx') or 0
|
38
|
+
|
39
|
+
if (ARGV[4] >= last_gidx) then
|
40
|
+
local sub_idx = redis.call( 'incr', 'jason:subscription:' .. ARGV[3] .. ':' .. ARGV[1] .. ':idx' )
|
41
|
+
return sub_idx
|
42
|
+
else
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
LUA
|
46
|
+
|
47
|
+
$redis_jason.eval cmd, [], [model_name, id, sub_id, gidx]
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_set_with_diff(key, add_members, remove_members)
|
51
|
+
cmd = <<~LUA
|
52
|
+
local old_members = redis.call('smembers', KEYS[1])
|
53
|
+
local add_size = ARGV[1]
|
54
|
+
|
55
|
+
for k, m in pairs({unpack(ARGV, 2, add_size + 1)}) do
|
56
|
+
redis.call('sadd', KEYS[1], m)
|
57
|
+
end
|
58
|
+
|
59
|
+
for k, m in pairs({unpack(ARGV, add_size + 2, #ARGV)}) do
|
60
|
+
redis.call('srem', KEYS[1], m)
|
61
|
+
end
|
62
|
+
|
63
|
+
return old_members
|
64
|
+
LUA
|
65
|
+
|
66
|
+
args = [add_members.size, add_members, remove_members].flatten
|
67
|
+
|
68
|
+
old_members = $redis_jason.eval cmd, [key], args
|
69
|
+
return [old_members, (old_members + add_members - remove_members).uniq]
|
70
|
+
end
|
71
|
+
end
|
data/lib/jason/publisher.rb
CHANGED
@@ -1,76 +1,121 @@
|
|
1
1
|
module Jason::Publisher
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
|
+
# Warning: Could be expensive. Mainly useful for rebuilding cache after changing Jason config or on deploy
|
5
|
+
def self.cache_all
|
6
|
+
Rails.application.eager_load!
|
7
|
+
ActiveRecord::Base.descendants.each do |klass|
|
8
|
+
klass.cache_all if klass.respond_to?(:cache_all)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
4
12
|
def cache_json
|
5
13
|
as_json_config = api_model.as_json_config
|
6
14
|
scope = api_model.scope
|
7
15
|
|
16
|
+
# Exists
|
8
17
|
if self.persisted? && (scope.blank? || self.class.unscoped.send(scope).exists?(self.id))
|
9
18
|
payload = self.reload.as_json(as_json_config)
|
10
|
-
|
19
|
+
gidx = Jason::LuaGenerator.new.cache_json(self.class.name.underscore, self.id, payload)
|
20
|
+
return [payload, gidx]
|
21
|
+
# Has been destroyed
|
11
22
|
else
|
12
|
-
$redis_jason.hdel("jason:#{self.class.name.underscore}
|
23
|
+
$redis_jason.hdel("jason:cache:#{self.class.name.underscore}", self.id)
|
24
|
+
return []
|
13
25
|
end
|
14
26
|
end
|
15
27
|
|
16
|
-
def
|
17
|
-
|
28
|
+
def force_publish_json
|
29
|
+
# As-if newly created
|
30
|
+
publish_json(self.attributes.map { |k,v| [k, [nil, v]] }.to_h)
|
31
|
+
end
|
32
|
+
|
33
|
+
def publish_json(previous_changes = {})
|
34
|
+
payload, gidx = cache_json
|
35
|
+
|
18
36
|
return if skip_publish_json
|
19
|
-
|
20
|
-
|
37
|
+
subs = jason_subscriptions # Get this first, because could be changed
|
38
|
+
|
39
|
+
# Situations where IDs may need to change and this can't be immediately determined
|
40
|
+
# - An instance is created where it belongs_to an instance under a subscription
|
41
|
+
# - An instance belongs_to association changes - e.g. comment.post_id changes to/from one with a subscription
|
42
|
+
# - TODO: The value of an instance changes so that it enters/leaves a subscription
|
43
|
+
|
44
|
+
# TODO: Optimize this, by caching associations rather than checking each time instance is saved
|
45
|
+
jason_assocs = self.class.reflect_on_all_associations(:belongs_to).select { |assoc| assoc.klass.respond_to?(:has_jason?) }
|
46
|
+
jason_assocs.each do |assoc|
|
47
|
+
if previous_changes[assoc.foreign_key].present?
|
48
|
+
Jason::Subscription.update_ids(
|
49
|
+
self.class.name.underscore,
|
50
|
+
id,
|
51
|
+
assoc.name.to_s.singularize,
|
52
|
+
previous_changes[assoc.foreign_key][0],
|
53
|
+
previous_changes[assoc.foreign_key][1]
|
54
|
+
)
|
55
|
+
elsif (persisted? && @was_a_new_record && send(assoc.foreign_key).present?)
|
56
|
+
Jason::Subscription.update_ids(
|
57
|
+
self.class.name.underscore,
|
58
|
+
id,
|
59
|
+
assoc.name.to_s.singularize,
|
60
|
+
nil,
|
61
|
+
send(assoc.foreign_key)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if !persisted? # Deleted
|
67
|
+
Jason::Subscription.remove_ids(
|
68
|
+
self.class.name.underscore,
|
69
|
+
[id]
|
70
|
+
)
|
71
|
+
end
|
21
72
|
|
22
|
-
|
23
|
-
|
73
|
+
# - An instance is created where it belongs_to an _all_ subscription
|
74
|
+
if previous_changes['id'].present?
|
75
|
+
Jason::Subscription.add_id(self.class.name.underscore, id)
|
76
|
+
end
|
77
|
+
|
78
|
+
if persisted?
|
79
|
+
jason_subscriptions.each do |sub_id|
|
80
|
+
Jason::Subscription.new(id: sub_id).update(self.class.name.underscore, id, payload, gidx)
|
24
81
|
end
|
25
82
|
end
|
26
83
|
end
|
27
84
|
|
28
85
|
def publish_json_if_changed
|
29
86
|
subscribed_fields = api_model.subscribed_fields
|
30
|
-
publish_json if (self.previous_changes.keys.map(&:to_sym) & subscribed_fields).present? || !self.persisted?
|
87
|
+
publish_json(self.previous_changes) if (self.previous_changes.keys.map(&:to_sym) & subscribed_fields).present? || !self.persisted?
|
31
88
|
end
|
32
89
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
90
|
+
def jason_subscriptions
|
91
|
+
Jason::Subscription.for_instance(self.class.name.underscore, id)
|
92
|
+
end
|
37
93
|
|
38
|
-
|
39
|
-
|
40
|
-
|
94
|
+
def jason_cached_value
|
95
|
+
JSON.parse($redis_jason.hget("jason:cache:#{self.class.name.underscore}", id) || '{}')
|
96
|
+
end
|
41
97
|
|
42
|
-
|
43
|
-
|
98
|
+
class_methods do
|
99
|
+
def cache_all
|
100
|
+
all.each(&:cache_json)
|
101
|
+
end
|
44
102
|
|
45
|
-
|
46
|
-
|
47
|
-
end
|
103
|
+
def has_jason?
|
104
|
+
true
|
48
105
|
end
|
49
106
|
|
50
107
|
def flush_cache
|
51
|
-
$redis_jason.del("jason:#{self.name.underscore}
|
108
|
+
$redis_jason.del("jason:cache:#{self.name.underscore}")
|
52
109
|
end
|
53
110
|
|
54
111
|
def setup_json
|
112
|
+
self.before_save -> {
|
113
|
+
@was_a_new_record = new_record?
|
114
|
+
}
|
55
115
|
self.after_initialize -> {
|
56
116
|
@api_model = Jason::ApiModel.new(self.class.name.underscore)
|
57
117
|
}
|
58
118
|
self.after_commit :publish_json_if_changed
|
59
|
-
|
60
|
-
include_models = Jason::ApiModel.new(self.name.underscore).include_models
|
61
|
-
|
62
|
-
include_models.map do |assoc|
|
63
|
-
puts assoc
|
64
|
-
reflection = self.reflect_on_association(assoc.to_sym)
|
65
|
-
reflection.klass.after_commit -> {
|
66
|
-
subscribed_fields = Jason::ApiModel.new(self.class.name.underscore).subscribed_fields
|
67
|
-
puts subscribed_fields.inspect
|
68
|
-
|
69
|
-
if (self.previous_changes.keys.map(&:to_sym) & subscribed_fields).present?
|
70
|
-
self.send(reflection.inverse_of.name)&.publish_json
|
71
|
-
end
|
72
|
-
}
|
73
|
-
end
|
74
119
|
end
|
75
120
|
|
76
121
|
def find_or_create_by_id(params)
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Jason::PublisherOld
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def cache_json
|
5
|
+
as_json_config = api_model.as_json_config
|
6
|
+
scope = api_model.scope
|
7
|
+
|
8
|
+
if self.persisted? && (scope.blank? || self.class.unscoped.send(scope).exists?(self.id))
|
9
|
+
payload = self.reload.as_json(as_json_config)
|
10
|
+
$redis_jason.hset("jason:#{self.class.name.underscore}:cache", self.id, payload.to_json)
|
11
|
+
else
|
12
|
+
$redis_jason.hdel("jason:#{self.class.name.underscore}:cache", self.id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def publish_json
|
17
|
+
cache_json
|
18
|
+
return if skip_publish_json
|
19
|
+
|
20
|
+
self.class.jason_subscriptions.each do |id, config_json|
|
21
|
+
config = JSON.parse(config_json)
|
22
|
+
|
23
|
+
if (config['conditions'] || {}).all? { |field, value| self.send(field) == value }
|
24
|
+
Jason::Subscription.new(id: id).update(self.class.name.underscore)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def publish_json_if_changed
|
30
|
+
subscribed_fields = api_model.subscribed_fields
|
31
|
+
publish_json if (self.previous_changes.keys.map(&:to_sym) & subscribed_fields).present? || !self.persisted?
|
32
|
+
end
|
33
|
+
|
34
|
+
class_methods do
|
35
|
+
def subscriptions
|
36
|
+
$redis_jason.hgetall("jason:#{self.name.underscore}:subscriptions")
|
37
|
+
end
|
38
|
+
|
39
|
+
def jason_subscriptions
|
40
|
+
$redis_jason.hgetall("jason:#{self.name.underscore}:subscriptions")
|
41
|
+
end
|
42
|
+
|
43
|
+
def publish_all(instances)
|
44
|
+
instances.each(&:cache_json)
|
45
|
+
|
46
|
+
subscriptions.each do |id, config_json|
|
47
|
+
Jason::Subscription.new(id: id).update(self.name.underscore)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def flush_cache
|
52
|
+
$redis_jason.del("jason:#{self.name.underscore}:cache")
|
53
|
+
end
|
54
|
+
|
55
|
+
def setup_json
|
56
|
+
self.after_initialize -> {
|
57
|
+
@api_model = Jason::ApiModel.new(self.class.name.underscore)
|
58
|
+
}
|
59
|
+
self.after_commit :publish_json_if_changed
|
60
|
+
|
61
|
+
include_models = Jason::ApiModel.new(self.name.underscore).include_models
|
62
|
+
|
63
|
+
include_models.map do |assoc|
|
64
|
+
puts assoc
|
65
|
+
reflection = self.reflect_on_association(assoc.to_sym)
|
66
|
+
reflection.klass.after_commit -> {
|
67
|
+
subscribed_fields = Jason::ApiModel.new(self.class.name.underscore).subscribed_fields
|
68
|
+
puts subscribed_fields.inspect
|
69
|
+
|
70
|
+
if (self.previous_changes.keys.map(&:to_sym) & subscribed_fields).present?
|
71
|
+
self.send(reflection.inverse_of.name)&.publish_json
|
72
|
+
end
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_or_create_by_id(params)
|
78
|
+
object = find_by(id: params[:id])
|
79
|
+
|
80
|
+
if object
|
81
|
+
object.update(params)
|
82
|
+
elsif params[:hidden]
|
83
|
+
return false ## If an object is passed with hidden = true but didn't already exist, it's safe to never create it
|
84
|
+
else
|
85
|
+
object = create!(params)
|
86
|
+
end
|
87
|
+
|
88
|
+
object
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_or_create_by_id!(params)
|
92
|
+
object = find_by(id: params[:id])
|
93
|
+
|
94
|
+
if object
|
95
|
+
object.update!(params)
|
96
|
+
elsif params[:hidden]
|
97
|
+
## TODO: We're diverging from semantics of the Rails bang! methods here, which would normally either raise or return an object. Find a way to make this better.
|
98
|
+
return false ## If an object is passed with hidden = true but didn't already exist, it's safe to never create it
|
99
|
+
else
|
100
|
+
object = create!(params)
|
101
|
+
end
|
102
|
+
|
103
|
+
object
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
included do
|
108
|
+
attr_accessor :skip_publish_json, :api_model
|
109
|
+
|
110
|
+
setup_json
|
111
|
+
end
|
112
|
+
end
|