smooth 2.0.1 → 2.0.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 +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'
|