smooth 2.0.1 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +7 -0
- data/Gemfile +1 -2
- data/README.md +150 -5
- data/Rakefile +16 -0
- data/app/assets/javascripts/smooth/index.js +5152 -0
- data/bin/smooth +9 -0
- data/{app/assets/javascripts/smooth → developer-tools}/.keep +0 -0
- data/developer-tools/bower.json +8 -0
- data/developer-tools/config.ru +3 -0
- data/developer-tools/dist/08d606864d3ad3f0b98660d391f5a1c2.gif +0 -0
- data/developer-tools/dist/2d66bcdc27cd89f71068e98a7a929712.gif +0 -0
- data/developer-tools/dist/3e9816417b11485d454f9b3662b06e7b.eot +0 -0
- data/developer-tools/dist/47de617fd1d745ad120ccb9e2924b98c.gif +0 -0
- data/developer-tools/dist/5ae23ad29b67289a1375d2043e289c52.eot +0 -0
- data/developer-tools/dist/60c2a8500e63bf211b7df9608f7613ea.svg +450 -0
- data/developer-tools/dist/645f50ba6c1e56f078fa018855d97eb0.gif +0 -0
- data/developer-tools/dist/71ab514d1cedda303417ad7a06472fea.ttf +0 -0
- data/developer-tools/dist/8cca2f02b0af2da365ff4d1755f29146.ttf +0 -0
- data/developer-tools/dist/939cf252f0eb4efbd2d170c974411c49.gif +0 -0
- data/developer-tools/dist/9af25aaeb6ca6d08d213b04841813eb5.gif +0 -0
- data/developer-tools/dist/b683029bafe0305ac2234038a03e1541.woff +0 -0
- data/developer-tools/dist/c9dec22105ad9330c811599b8b6464f8.woff +0 -0
- data/developer-tools/dist/ca279c55a51ab2641c4712a333633581.gif +0 -0
- data/developer-tools/dist/client.js +5152 -0
- data/developer-tools/dist/f5b27137d3f5e9b1d91b16b37386dd03.gif +0 -0
- data/developer-tools/dist/f99a231ed57ee113b50b1c3e9f9fcdc3.svg +399 -0
- data/developer-tools/dist/index.html +18 -0
- data/developer-tools/dist/inspector.js +38432 -0
- data/developer-tools/dist/jquery.min.js +9190 -0
- data/developer-tools/package.json +39 -0
- data/developer-tools/server.js +14 -0
- data/developer-tools/src/client.coffee +21 -0
- data/developer-tools/src/client/collection.coffee +14 -0
- data/developer-tools/src/client/model.coffee +11 -0
- data/developer-tools/src/client/resource.coffee +132 -0
- data/{app/controllers/.keep → developer-tools/src/client/runner.coffee} +0 -0
- data/developer-tools/src/dependencies.coffee +7 -0
- data/developer-tools/src/inspector.cjsx +49 -0
- data/developer-tools/src/inspector/models/interface_collection.coffee +31 -0
- data/developer-tools/src/inspector/pages/index.cjsx +31 -0
- data/developer-tools/src/inspector/pages/resources.cjsx +5 -0
- data/developer-tools/src/inspector/views/grid_sort.cjsx +23 -0
- data/developer-tools/src/inspector/views/icon_heading.cjsx +15 -0
- data/developer-tools/src/inspector/views/resource_card.cjsx +34 -0
- data/developer-tools/src/inspector/views/sidebar.cjsx +12 -0
- data/developer-tools/src/inspector/views/toolbar.cjsx +17 -0
- data/developer-tools/src/styles/index.scss +136 -0
- data/developer-tools/src/styles/views.scss +13 -0
- data/developer-tools/src/util.coffee +48 -0
- data/developer-tools/webpack.config.js +56 -0
- data/developer-tools/webpack.hot.config.js +65 -0
- data/lib/smooth.rb +209 -28
- data/lib/smooth/active_record/adapter.rb +24 -0
- data/lib/smooth/api.rb +272 -18
- data/lib/smooth/api/policy.rb +2 -2
- data/lib/smooth/api/tracking.rb +4 -4
- data/lib/smooth/application.rb +66 -0
- data/lib/smooth/cache.rb +1 -1
- data/lib/smooth/command.rb +267 -18
- data/lib/smooth/command/async_worker.rb +27 -0
- data/lib/smooth/command/instrumented.rb +6 -4
- data/lib/smooth/command/run_proxy.rb +21 -0
- data/lib/smooth/configuration.rb +63 -8
- data/lib/smooth/documentation.rb +3 -6
- data/lib/smooth/dsl.rb +1 -36
- data/lib/smooth/dsl_adapter.rb +34 -0
- data/lib/smooth/event.rb +8 -4
- data/lib/smooth/event/proxy.rb +9 -0
- data/lib/smooth/event/relay.rb +38 -0
- data/lib/smooth/example.rb +1 -1
- data/lib/smooth/ext/core.rb +16 -0
- data/lib/smooth/model_adapter.rb +31 -0
- data/lib/smooth/query.rb +143 -13
- data/lib/smooth/resource.rb +227 -52
- data/lib/smooth/resource/router.rb +217 -0
- data/lib/smooth/resource/templating.rb +62 -0
- data/lib/smooth/resource/tracking.rb +1 -1
- data/lib/smooth/response.rb +73 -0
- data/lib/smooth/serializer.rb +102 -11
- data/lib/smooth/user_adapter.rb +83 -0
- data/lib/smooth/util.rb +17 -0
- data/lib/smooth/version.rb +1 -1
- data/smooth.gemspec +6 -2
- data/spec/acceptance/books_routes_spec.rb +50 -0
- data/spec/acceptance/embedded_relationships_spec.rb +26 -0
- data/spec/dummy/app/apis/application_api.rb +8 -3
- data/spec/dummy/app/commands/create_book.rb +5 -0
- data/spec/dummy/app/models/book.rb +1 -0
- data/spec/dummy/app/models/library.rb +2 -0
- data/spec/dummy/app/models/user.rb +2 -0
- data/spec/dummy/app/queries/book_query.rb +13 -0
- data/spec/dummy/app/resources/{books.rb → books_definition.rb} +37 -12
- data/spec/dummy/db/migrate/20140824215902_create_users.rb +10 -0
- data/spec/dummy/db/migrate/20140826193259_create_libraries.rb +10 -0
- data/spec/dummy/db/schema.rb +8 -1
- data/spec/lib/smooth/api/async_spec.rb +21 -0
- data/spec/lib/smooth/api_spec.rb +8 -0
- data/spec/lib/smooth/command_spec.rb +87 -6
- data/spec/lib/smooth/configuration_spec.rb +4 -0
- data/spec/lib/smooth/event/relay_spec.rb +33 -0
- data/spec/lib/smooth/event_spec.rb +5 -8
- data/spec/lib/smooth/query_spec.rb +42 -0
- data/spec/lib/smooth/resource/router_spec.rb +14 -0
- data/spec/lib/smooth/resource_spec.rb +33 -1
- data/spec/lib/smooth/serializer_spec.rb +20 -0
- data/spec/lib/smooth/templating_spec.rb +23 -0
- data/spec/lib/smooth/util_spec.rb +22 -0
- data/spec/spec_helper.rb +1 -1
- metadata +151 -17
- data/app/helpers/.keep +0 -0
- data/app/mailers/.keep +0 -0
- data/app/models/.keep +0 -0
- data/app/views/.keep +0 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
data/lib/smooth/documentation.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
module Smooth
|
2
2
|
module Documentation
|
3
|
-
|
4
|
-
def self.included base
|
5
|
-
binding.pry
|
6
|
-
|
3
|
+
def self.included(base)
|
7
4
|
base.class_eval do
|
8
5
|
attr_accessor :_inline_description
|
9
6
|
|
@@ -15,7 +12,7 @@ module Smooth
|
|
15
12
|
base.extend Smooth::Documentation
|
16
13
|
end
|
17
14
|
|
18
|
-
def desc
|
15
|
+
def desc(description, *args)
|
19
16
|
self._inline_description = {
|
20
17
|
description: description,
|
21
18
|
args: args
|
@@ -23,7 +20,7 @@ module Smooth
|
|
23
20
|
end
|
24
21
|
|
25
22
|
def inline_description
|
26
|
-
val =
|
23
|
+
val = _inline_description && _inline_description.dup
|
27
24
|
self._inline_description = nil
|
28
25
|
val
|
29
26
|
end
|
data/lib/smooth/dsl.rb
CHANGED
@@ -1,36 +1 @@
|
|
1
|
-
|
2
|
-
module Dsl
|
3
|
-
# Creates or opens an API definition
|
4
|
-
def api name, *args, &block
|
5
|
-
Smooth.current_api_name = name
|
6
|
-
|
7
|
-
instance = Smooth.fetch_api(name) do |key|
|
8
|
-
options = args.dup.extract_options!
|
9
|
-
|
10
|
-
Smooth::Api.new(name, options).tap do |obj|
|
11
|
-
obj.instance_eval(&block) if block_given?
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
instance
|
16
|
-
end
|
17
|
-
|
18
|
-
# Creates or opens a resource definition
|
19
|
-
def resource name, *args, &block
|
20
|
-
options = args.extract_options!
|
21
|
-
|
22
|
-
api = case
|
23
|
-
when options[:api].is_a?(Symbol) || options[:api].is_a?(String)
|
24
|
-
Smooth.fetch_api(options[:api])
|
25
|
-
when options[:api].is_a?(Smooth::Api)
|
26
|
-
options[:api]
|
27
|
-
else
|
28
|
-
Smooth.current_api
|
29
|
-
end
|
30
|
-
|
31
|
-
api.resource(name, options, &block)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
extend(Smooth::Dsl)
|
1
|
+
extend(Smooth::DslAdapter)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Smooth
|
2
|
+
module DslAdapter
|
3
|
+
# Creates or opens an API definition
|
4
|
+
def api(name, *args, &block)
|
5
|
+
Smooth.current_api_name = name
|
6
|
+
|
7
|
+
config_block = block_given?
|
8
|
+
|
9
|
+
instance = Smooth.fetch_api(name) do |_key|
|
10
|
+
options = args.dup.extract_options!
|
11
|
+
|
12
|
+
Smooth::Api.new(name, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
instance.tap { |obj| obj.instance_eval(&block) if config_block }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Creates or opens a resource definition
|
19
|
+
def resource(name, *args, &block)
|
20
|
+
options = args.extract_options!
|
21
|
+
|
22
|
+
api = case
|
23
|
+
when options[:api].is_a?(Symbol) || options[:api].is_a?(String)
|
24
|
+
Smooth.fetch_api(options[:api])
|
25
|
+
when options[:api].is_a?(Smooth::Api)
|
26
|
+
options[:api]
|
27
|
+
else
|
28
|
+
Smooth.current_api
|
29
|
+
end
|
30
|
+
|
31
|
+
api.resource(name, options, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/smooth/event.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
require 'smooth/event/relay'
|
2
|
+
require 'smooth/event/proxy'
|
3
|
+
|
1
4
|
module Smooth
|
2
5
|
class Event < ActiveSupport::Notifications::Event
|
3
|
-
|
4
6
|
def self.provider
|
5
7
|
ActiveSupport::Notifications
|
6
8
|
end
|
@@ -11,13 +13,15 @@ module Smooth
|
|
11
13
|
end
|
12
14
|
|
13
15
|
module Adapter
|
14
|
-
def track_event
|
16
|
+
def track_event(*args, &_block)
|
15
17
|
Smooth::Event.provider.send(:instrument, *args)
|
16
18
|
end
|
17
19
|
|
18
|
-
def subscribe_to
|
20
|
+
def subscribe_to(event_name, aggregator = nil, &block)
|
19
21
|
Smooth::Event.provider.subscribe(event_name) do |*args|
|
20
|
-
|
22
|
+
event = Smooth::Event.new(*args)
|
23
|
+
aggregator << event if aggregator.respond_to?(:<<)
|
24
|
+
block.call(event, event_name) if block.respond_to?(:call)
|
21
25
|
end
|
22
26
|
end
|
23
27
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# The Smooth::Event::Relay listens for events on any Smooth::Event notifications channel
|
2
|
+
# It takes the event, performs some optional processing on it, and then relays information
|
3
|
+
# about that event to some other service. For example, a websocket or another event tracking api.
|
4
|
+
module Smooth
|
5
|
+
class Event < ActiveSupport::Notifications::Event
|
6
|
+
class Relay
|
7
|
+
attr_reader :event_name,
|
8
|
+
:options,
|
9
|
+
:system
|
10
|
+
|
11
|
+
def initialize(event_name, options = {})
|
12
|
+
@event_name = event_name
|
13
|
+
@options = options
|
14
|
+
@system = options.fetch(:system, Smooth::Event)
|
15
|
+
|
16
|
+
enable
|
17
|
+
end
|
18
|
+
|
19
|
+
def relay(_event, _event_name = nil)
|
20
|
+
# IMPLEMENT IN YOUR OWN CLASS
|
21
|
+
fail NotImplementedError
|
22
|
+
end
|
23
|
+
|
24
|
+
def process(event, event_name = nil)
|
25
|
+
[event, event_name]
|
26
|
+
end
|
27
|
+
|
28
|
+
def enable
|
29
|
+
@subscriber ||= system.subscribe_to(event_name, &method(:process_and_relay))
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_and_relay(event, event_name = nil)
|
33
|
+
event, event_name = process(event, event_name)
|
34
|
+
relay(event, event_name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/smooth/example.rb
CHANGED
data/lib/smooth/ext/core.rb
CHANGED
@@ -3,3 +3,19 @@ class Hash
|
|
3
3
|
Hashie::Mash.new(dup)
|
4
4
|
end
|
5
5
|
end
|
6
|
+
|
7
|
+
class NilClass
|
8
|
+
def empty?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class String
|
14
|
+
def empty?
|
15
|
+
length == 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.random_token(length = 12)
|
19
|
+
rand(36**36).to_s(36).slice(0, length)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Smooth
|
2
|
+
module ModelAdapter
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
def query(current_user, params = {})
|
8
|
+
self.class.smooth_resource.fetch(:query, :default)
|
9
|
+
.as(current_user)
|
10
|
+
.run(params)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def acts_smooth(_options = {}, &block)
|
15
|
+
@smooth_resource ||= begin
|
16
|
+
resource_name = to_s.split('::').last.to_s.pluralize
|
17
|
+
Smooth.resource(resource_name, model: self, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Because it depends how you feel.
|
22
|
+
def acts_real_smooth(options = {}, &block)
|
23
|
+
acts_smooth(options, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def smooth_resource
|
27
|
+
@smooth_resource || acts_as_smooth
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/smooth/query.rb
CHANGED
@@ -1,15 +1,80 @@
|
|
1
1
|
module Smooth
|
2
|
-
class Query
|
2
|
+
class Query < Smooth::Command
|
3
3
|
include Smooth::Documentation
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
# To customize the filtering behavior of the query
|
6
|
+
# you can supply your own execute method body. The
|
7
|
+
# execute method is expected to mutate the value of the
|
8
|
+
# `self.scope` or @scope instance variable and to return
|
9
|
+
# something which can be serialized as JSON using the
|
10
|
+
# Smooth::Serializer
|
11
|
+
def execute
|
12
|
+
apply_filters
|
13
|
+
scope
|
14
|
+
end
|
15
|
+
|
16
|
+
def apply_filters
|
17
|
+
params.each(&method(:apply_filter))
|
18
|
+
|
19
|
+
if raw_inputs['ids']
|
20
|
+
ids = raw_inputs['ids']
|
21
|
+
ids = ids.split(',') if ids.is_a?(String)
|
22
|
+
|
23
|
+
self.scope = scope.where(id: Array(ids))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def operator_for(filter)
|
34
|
+
specific = interface_for(filter).options.operator
|
35
|
+
return specific if specific
|
36
|
+
:eq
|
37
|
+
end
|
38
|
+
|
39
|
+
def operator_and_type_for(filter)
|
40
|
+
[operator_for(filter), interface_for(filter).type]
|
41
|
+
end
|
42
|
+
|
43
|
+
def column_for(key)
|
44
|
+
interface_for(key).options.column || key
|
45
|
+
end
|
46
|
+
|
47
|
+
def apply_filter(*parts)
|
48
|
+
key, value = parts.flatten
|
49
|
+
|
50
|
+
operator = operator_for(key)
|
51
|
+
|
52
|
+
value = "%#{value}%" if operator == :like
|
53
|
+
value = "#{value}%" if operator == :ends_with
|
54
|
+
value = "%#{value}" if operator == :begins_with
|
55
|
+
|
56
|
+
operator = :matches if operator == :like
|
57
|
+
|
58
|
+
column = column_for(key)
|
59
|
+
condition = arel_table[column].send(operator, value)
|
60
|
+
|
61
|
+
self.scope = scope.merge(scope.where(condition))
|
62
|
+
end
|
63
|
+
|
64
|
+
def arel_table
|
65
|
+
model_class.arel_table
|
66
|
+
end
|
7
67
|
|
8
|
-
|
68
|
+
class_attribute :query_config,
|
69
|
+
:parent_resource
|
70
|
+
|
71
|
+
self.query_config = Hashie::Mash.new(base: {})
|
72
|
+
|
73
|
+
def self.configure(dsl_config_object, resource = nil)
|
9
74
|
resource ||= Smooth.current_resource
|
10
|
-
klass = define_or_open(
|
75
|
+
klass = define_or_open(dsl_config_object, resource)
|
11
76
|
|
12
|
-
Array(
|
77
|
+
Array(dsl_config_object.blocks).each do |blk|
|
13
78
|
klass.class_eval(&blk)
|
14
79
|
end
|
15
80
|
|
@@ -21,28 +86,47 @@ module Smooth
|
|
21
86
|
base = Smooth.query
|
22
87
|
|
23
88
|
name = options.name
|
24
|
-
name = nil if name ==
|
89
|
+
name = nil if name == 'Default'
|
90
|
+
|
91
|
+
klass = "#{ resource.model_class }#{ name }".singularize + 'Query'
|
25
92
|
|
26
|
-
klass =
|
93
|
+
klass = klass.gsub(/\s+/, '')
|
94
|
+
|
95
|
+
apply_options = lambda do |k|
|
96
|
+
k.model_class ||= resource.model_class if resource.model_class
|
97
|
+
|
98
|
+
k.resource_name = resource.name.to_s if k.resource_name.empty?
|
99
|
+
k.command_action = 'query' if k.command_action.empty?
|
100
|
+
k.belongs_to_resource(resource)
|
101
|
+
end
|
27
102
|
|
28
103
|
if query_klass = Object.const_get(klass) rescue nil
|
29
|
-
return query_klass
|
104
|
+
return query_klass.tap(&apply_options)
|
30
105
|
end
|
31
106
|
|
32
|
-
|
107
|
+
begin
|
108
|
+
Object.const_set(klass, Class.new(base)).tap(&apply_options)
|
109
|
+
rescue
|
110
|
+
binding.pry
|
111
|
+
end
|
33
112
|
end
|
34
113
|
|
35
|
-
def self.start_from
|
114
|
+
def self.start_from(*args, &_block)
|
36
115
|
options = args.extract_options!
|
37
116
|
config.start_from = options
|
38
117
|
end
|
39
118
|
|
40
|
-
def
|
119
|
+
def params
|
120
|
+
inputs
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.params(*args, &block)
|
41
124
|
options = args.extract_options!
|
42
125
|
config.params = options
|
126
|
+
send(:optional, *args, &block)
|
43
127
|
end
|
44
128
|
|
45
|
-
def self.role
|
129
|
+
def self.role(name, &block)
|
46
130
|
@current_config = name
|
47
131
|
instance_eval(&block) if block_given?
|
48
132
|
end
|
@@ -58,5 +142,51 @@ module Smooth
|
|
58
142
|
val
|
59
143
|
end
|
60
144
|
|
145
|
+
def self.respond_to_find_request(request_object, _options = {})
|
146
|
+
outcome = as(request_object.user).run(request_object.params)
|
147
|
+
|
148
|
+
Smooth::Response.new(nil).tap do |response|
|
149
|
+
response.command_action = :find
|
150
|
+
response.event_namespace = event_namespace
|
151
|
+
response.request_headers = request_object.headers
|
152
|
+
|
153
|
+
if outcome.success?
|
154
|
+
response.object = outcome.result.find(request_object.params[:id])
|
155
|
+
response.success = true
|
156
|
+
response.serializer = find_serializer_for(request_object)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.response_class
|
162
|
+
Smooth::Query::Response
|
163
|
+
end
|
164
|
+
|
165
|
+
class Response < Smooth::Response
|
166
|
+
def serializer
|
167
|
+
if command_action.to_sym == :find
|
168
|
+
@serializer
|
169
|
+
else
|
170
|
+
Smooth::ArraySerializer
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def options
|
175
|
+
@serializer_options.tap do |o|
|
176
|
+
o[:each_serializer] = @serializer unless command_action == :find
|
177
|
+
o[:scope] = current_user
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def object
|
182
|
+
return @object if @object
|
183
|
+
|
184
|
+
if command_action.to_sym == :find
|
185
|
+
outcome.result
|
186
|
+
elsif success? && command_action.to_sym == :query
|
187
|
+
outcome.result.to_a
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
61
191
|
end
|
62
192
|
end
|
data/lib/smooth/resource.rb
CHANGED
@@ -1,47 +1,137 @@
|
|
1
|
+
require 'smooth/resource/templating'
|
2
|
+
|
1
3
|
module Smooth
|
2
4
|
class Resource
|
3
5
|
include Smooth::Documentation
|
6
|
+
include Smooth::Resource::Templating
|
4
7
|
|
5
8
|
attr_accessor :resource_name,
|
6
|
-
:api_name
|
9
|
+
:api_name,
|
10
|
+
:model_class,
|
11
|
+
:group_description,
|
12
|
+
:description
|
7
13
|
|
8
14
|
# These store the configuration values for the various
|
9
15
|
# objects belonging to the resource.
|
10
|
-
attr_reader
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
attr_reader :_queries,
|
17
|
+
:_commands,
|
18
|
+
:_serializers,
|
19
|
+
:_routes,
|
20
|
+
:_examples,
|
21
|
+
:object_descriptions
|
22
|
+
|
23
|
+
def initialize(resource_name, options = {}, &block)
|
24
|
+
@resource_name = resource_name
|
25
|
+
@options = options
|
26
|
+
|
27
|
+
@model_class = options.fetch(:model, nil)
|
28
|
+
|
29
|
+
@_serializers = {}.to_mash
|
30
|
+
@_commands = {}.to_mash
|
31
|
+
@_queries = {}.to_mash
|
32
|
+
@_routes = {}.to_mash
|
33
|
+
@_examples = {}.to_mash
|
34
|
+
|
35
|
+
@object_descriptions = {
|
36
|
+
commands: {},
|
37
|
+
queries: {},
|
38
|
+
serializers: {},
|
39
|
+
routes: {},
|
40
|
+
examples: {}
|
41
|
+
}
|
42
|
+
|
43
|
+
@loaded = false
|
44
|
+
|
45
|
+
instance_eval(&block) if block_given?
|
15
46
|
|
16
|
-
|
17
|
-
|
18
|
-
@options = options
|
47
|
+
load!
|
48
|
+
end
|
19
49
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
50
|
+
# The Smooth Resources are capable of exposing information
|
51
|
+
# about their configuration, which makes auto generating documentation
|
52
|
+
# for their API very easy, but this can also be used to generate
|
53
|
+
# client side models or control form / report builder interfaces.
|
54
|
+
def interface_documentation
|
55
|
+
resource = self
|
25
56
|
|
26
|
-
@
|
57
|
+
@interface ||= begin
|
58
|
+
base = {
|
59
|
+
routes: (router.interface_documentation rescue {})
|
60
|
+
}
|
61
|
+
|
62
|
+
resource.object_descriptions.keys.reduce(base) do |memo, type|
|
63
|
+
memo.tap do
|
64
|
+
bucket = memo[type] ||= {}
|
65
|
+
resource.send("available_#{ type }").each do |object_name|
|
66
|
+
docs = resource.expanded_documentation_for(type, object_name)
|
67
|
+
bucket[object_name] = docs
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end.to_mash
|
72
|
+
end
|
27
73
|
|
28
|
-
|
74
|
+
# Resource groups allow for easier organization of the
|
75
|
+
# documentation and things of that nature.
|
76
|
+
def part_of_the(group_description)
|
77
|
+
@group_description = group_description
|
78
|
+
end
|
29
79
|
|
30
|
-
|
80
|
+
def available_commands
|
81
|
+
_commands.keys
|
82
|
+
end
|
83
|
+
|
84
|
+
def available_queries
|
85
|
+
_queries.keys
|
86
|
+
end
|
87
|
+
|
88
|
+
def available_serializers
|
89
|
+
_serializers.keys
|
90
|
+
end
|
91
|
+
|
92
|
+
# SHORT CIRCUIT
|
93
|
+
def available_routes
|
94
|
+
[]
|
95
|
+
end
|
96
|
+
|
97
|
+
def available_examples
|
98
|
+
_examples.keys
|
99
|
+
end
|
100
|
+
|
101
|
+
def model_class
|
102
|
+
@model_class || (resource_name.singularize.constantize rescue nil)
|
31
103
|
end
|
32
104
|
|
33
105
|
def name
|
34
106
|
resource_name
|
35
107
|
end
|
36
108
|
|
37
|
-
def
|
109
|
+
def has_many(*args)
|
110
|
+
model_class.send(:has_many, *args)
|
111
|
+
end
|
112
|
+
|
113
|
+
def belongs_to(*args)
|
114
|
+
model_class.send(:belongs_to, *args)
|
115
|
+
end
|
116
|
+
|
117
|
+
def has_one(*args)
|
118
|
+
model_class.send(:has_one, *args)
|
119
|
+
end
|
120
|
+
|
121
|
+
def has_and_belongs_to_many(*args)
|
122
|
+
model_class.send(:has_and_belongs_to_many, *args)
|
123
|
+
end
|
124
|
+
|
125
|
+
def fetch_config(object_type, object_key)
|
38
126
|
source = send("_#{ object_type }s") rescue nil
|
39
|
-
source
|
127
|
+
source = @_queries if object_type.to_sym == :query
|
128
|
+
source && source.fetch(object_key.to_s.downcase.to_sym)
|
40
129
|
end
|
41
130
|
|
42
|
-
def fetch
|
131
|
+
def fetch(object_type, object_key)
|
43
132
|
source = instance_variable_get("@#{ object_type }s") rescue nil
|
44
|
-
source
|
133
|
+
source = @queries if object_type.to_sym == :query
|
134
|
+
source && source.fetch(object_key.to_s.downcase)
|
45
135
|
end
|
46
136
|
|
47
137
|
def loaded?
|
@@ -60,82 +150,163 @@ module Smooth
|
|
60
150
|
Smooth.fetch_api(api_name || :default)
|
61
151
|
end
|
62
152
|
|
63
|
-
def apply_options
|
153
|
+
def apply_options(*opts)
|
64
154
|
@options.send(:merge!, *opts)
|
65
155
|
end
|
66
156
|
|
67
|
-
def
|
157
|
+
def describe_object(object_type, object_name, with_value = {})
|
158
|
+
bucket = documentation_for(object_type, object_name)
|
159
|
+
|
160
|
+
with_value = { description: with_value } if with_value.is_a?(String)
|
161
|
+
|
162
|
+
bucket[:description] = with_value.fetch(:description, with_value['description'])
|
163
|
+
bucket[:description_args] = with_value.fetch(:args, [])
|
164
|
+
end
|
165
|
+
|
166
|
+
def documentation_for(object_type, object_name)
|
167
|
+
object_descriptions[object_type.to_s.pluralize.to_sym][object_name.to_sym] ||= {}
|
168
|
+
end
|
169
|
+
|
170
|
+
def expanded_documentation_for(object_type, object_name)
|
171
|
+
base = documentation_for(object_type, object_name)
|
172
|
+
klass = object_class_for(object_type, object_name)
|
173
|
+
|
174
|
+
base.merge!(class: klass.to_s, interface: klass && klass.interface_documentation)
|
175
|
+
end
|
176
|
+
|
177
|
+
def object_class_for(object_type, object_name)
|
178
|
+
fetch(object_type.to_s.singularize.to_sym, object_name.to_sym)
|
179
|
+
rescue => e
|
180
|
+
binding.pry
|
181
|
+
end
|
182
|
+
|
183
|
+
def serializer(serializer_name = 'Default', *args, &block)
|
68
184
|
if args.empty? && !block_given? && exists = fetch(:serializer, serializer_name)
|
69
185
|
return exists
|
70
186
|
end
|
71
187
|
|
72
188
|
options = args.extract_options!
|
73
189
|
|
74
|
-
|
75
|
-
args.first || inline_description
|
76
|
-
end
|
190
|
+
provided_description = options.fetch(:description, inline_description)
|
77
191
|
|
78
|
-
|
79
|
-
config.description = description unless description.nil?
|
192
|
+
describe_object(:serializer, serializer_name.downcase, provided_description) unless provided_description.empty?
|
80
193
|
|
81
|
-
|
194
|
+
specified_class = args.first
|
195
|
+
specified_class = specified_class.constantize if specified_class.is_a?(String)
|
196
|
+
specified_class = nil if specified_class && !(specified_class <= Smooth.config.serializer_class)
|
197
|
+
|
198
|
+
config = _serializers[serializer_name.downcase.to_sym] ||= Hashie::Mash.new(options: {}, name: serializer_name, blocks: [block].compact, class: specified_class)
|
199
|
+
config.description = provided_description unless provided_description.nil?
|
82
200
|
end
|
83
201
|
|
84
|
-
def command
|
202
|
+
def command(command_name, *args, &block)
|
85
203
|
if args.empty? && !block_given? && exists = fetch(:command, command_name)
|
86
204
|
return exists
|
87
205
|
end
|
88
206
|
|
89
207
|
options = args.extract_options!
|
90
208
|
|
91
|
-
|
92
|
-
|
93
|
-
|
209
|
+
provided_description = options.fetch(:description, inline_description)
|
210
|
+
|
211
|
+
describe_object(:command, command_name.downcase, provided_description) unless provided_description.empty?
|
94
212
|
|
95
|
-
|
213
|
+
specified_class = args.first
|
214
|
+
specified_class = (specified_class.constantize rescue nil) if specified_class.is_a?(String)
|
215
|
+
specified_class = nil if specified_class && !(specified_class <= Smooth.config.command_class)
|
216
|
+
|
217
|
+
config = _commands[command_name.to_sym] ||= Hashie::Mash.new(options: {}, name: command_name, blocks: [block].compact, class: specified_class)
|
96
218
|
|
97
219
|
config.options.merge!(options)
|
98
|
-
config.description =
|
220
|
+
config.description = provided_description unless provided_description.nil?
|
99
221
|
|
100
222
|
config
|
101
223
|
end
|
102
224
|
|
103
|
-
def query
|
225
|
+
def query(query_name = 'Default', *args, &block)
|
104
226
|
if args.empty? && !block_given? && exists = fetch(:query, query_name)
|
105
227
|
return exists
|
106
228
|
end
|
107
229
|
|
108
230
|
options = args.extract_options!
|
109
231
|
|
110
|
-
|
111
|
-
args.first || inline_description
|
112
|
-
end
|
232
|
+
provided_description = options.fetch(:description, inline_description)
|
113
233
|
|
114
|
-
|
234
|
+
describe_object(:query, query_name.downcase, provided_description) unless provided_description.empty?
|
235
|
+
|
236
|
+
specified_class = args.first
|
237
|
+
specified_class = (specified_class.constantize rescue nil) if specified_class.is_a?(String)
|
238
|
+
specified_class = nil if specified_class && !(specified_class <= Smooth.config.query_class)
|
239
|
+
|
240
|
+
config = _queries[query_name.downcase.to_sym] ||= Hashie::Mash.new(options: {}, name: query_name, blocks: [block].compact, class: specified_class)
|
115
241
|
config.options.merge!(options)
|
116
|
-
config.description =
|
242
|
+
config.description = provided_description unless provided_description.nil?
|
117
243
|
|
118
244
|
config
|
119
245
|
end
|
120
246
|
|
121
|
-
def routes &block
|
122
|
-
return @
|
247
|
+
def routes(options = {}, &block)
|
248
|
+
return @router unless block_given?
|
249
|
+
|
250
|
+
@router ||= Smooth::Resource::Router.new(self, options).tap do |router|
|
251
|
+
router.instance_eval(&block)
|
252
|
+
router.build_methods_table
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def model(&block)
|
257
|
+
model_class.instance_eval(&block)
|
258
|
+
end
|
259
|
+
|
260
|
+
def scope(*args, &block)
|
261
|
+
model_class.send(:scope, *args, &block)
|
262
|
+
end
|
263
|
+
|
264
|
+
def router
|
265
|
+
@router || routes
|
266
|
+
end
|
267
|
+
|
268
|
+
def route_table
|
269
|
+
router.route_table
|
270
|
+
end
|
271
|
+
|
272
|
+
def expand_routes(from_attributes = {})
|
273
|
+
router.expand_routes(from_attributes)
|
123
274
|
end
|
124
275
|
|
125
|
-
def examples
|
276
|
+
def examples(options = {}, &_block)
|
126
277
|
if options.empty? && !block_given?
|
127
278
|
return @examples
|
128
279
|
end
|
129
280
|
end
|
130
281
|
|
282
|
+
def serializer_class(reference = :default)
|
283
|
+
@serializers.fetch(reference, serializer_classes.first)
|
284
|
+
end
|
285
|
+
|
286
|
+
def query_class(reference = :default)
|
287
|
+
@queries.fetch(reference, query_classes.first)
|
288
|
+
end
|
289
|
+
|
290
|
+
def serializer_classes
|
291
|
+
@serializers && @serializers.values
|
292
|
+
end
|
293
|
+
|
294
|
+
def query_classes
|
295
|
+
@queries && @queries.values
|
296
|
+
end
|
297
|
+
|
298
|
+
def command_classes
|
299
|
+
@commands && @commands.values
|
300
|
+
end
|
301
|
+
|
131
302
|
protected
|
132
303
|
|
133
304
|
def configure_commands
|
134
305
|
resource = self
|
135
306
|
|
136
|
-
@commands = _commands.
|
307
|
+
@commands = _commands.reduce({}.to_mash) do |memo, p|
|
137
308
|
ref, cfg = p
|
138
|
-
memo[cfg.name] = Smooth::Command.configure(cfg, resource)
|
309
|
+
memo[cfg.name.downcase] = Smooth::Command.configure(cfg, resource)
|
139
310
|
memo
|
140
311
|
end
|
141
312
|
end
|
@@ -143,19 +314,22 @@ module Smooth
|
|
143
314
|
def configure_serializers
|
144
315
|
resource = self
|
145
316
|
|
146
|
-
@serializers = _serializers.
|
147
|
-
|
148
|
-
|
149
|
-
|
317
|
+
@serializers = _serializers.reduce({}.to_mash) do |memo, p|
|
318
|
+
memo.tap do
|
319
|
+
ref, cfg = p
|
320
|
+
serializer = memo[cfg.name.downcase] = Smooth::Serializer.configure(cfg, resource)
|
321
|
+
|
322
|
+
serializer.return_ids_for_relationships! if Smooth.config.embed_relationships_as == :ids
|
323
|
+
end
|
150
324
|
end
|
151
325
|
end
|
152
326
|
|
153
327
|
def configure_queries
|
154
328
|
resource = self
|
155
329
|
|
156
|
-
@queries = _queries.
|
330
|
+
@queries = _queries.reduce({}.to_mash) do |memo, p|
|
157
331
|
ref, cfg = p
|
158
|
-
memo[cfg.name] = Smooth::Query.configure(cfg, resource)
|
332
|
+
memo[cfg.name.downcase] = Smooth::Query.configure(cfg, resource)
|
159
333
|
memo
|
160
334
|
end
|
161
335
|
end
|
@@ -171,3 +345,4 @@ module Smooth
|
|
171
345
|
end
|
172
346
|
|
173
347
|
require 'smooth/resource/tracking'
|
348
|
+
require 'smooth/resource/router'
|