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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -0
  3. data/Gemfile +1 -2
  4. data/README.md +150 -5
  5. data/Rakefile +16 -0
  6. data/app/assets/javascripts/smooth/index.js +5152 -0
  7. data/bin/smooth +9 -0
  8. data/{app/assets/javascripts/smooth → developer-tools}/.keep +0 -0
  9. data/developer-tools/bower.json +8 -0
  10. data/developer-tools/config.ru +3 -0
  11. data/developer-tools/dist/08d606864d3ad3f0b98660d391f5a1c2.gif +0 -0
  12. data/developer-tools/dist/2d66bcdc27cd89f71068e98a7a929712.gif +0 -0
  13. data/developer-tools/dist/3e9816417b11485d454f9b3662b06e7b.eot +0 -0
  14. data/developer-tools/dist/47de617fd1d745ad120ccb9e2924b98c.gif +0 -0
  15. data/developer-tools/dist/5ae23ad29b67289a1375d2043e289c52.eot +0 -0
  16. data/developer-tools/dist/60c2a8500e63bf211b7df9608f7613ea.svg +450 -0
  17. data/developer-tools/dist/645f50ba6c1e56f078fa018855d97eb0.gif +0 -0
  18. data/developer-tools/dist/71ab514d1cedda303417ad7a06472fea.ttf +0 -0
  19. data/developer-tools/dist/8cca2f02b0af2da365ff4d1755f29146.ttf +0 -0
  20. data/developer-tools/dist/939cf252f0eb4efbd2d170c974411c49.gif +0 -0
  21. data/developer-tools/dist/9af25aaeb6ca6d08d213b04841813eb5.gif +0 -0
  22. data/developer-tools/dist/b683029bafe0305ac2234038a03e1541.woff +0 -0
  23. data/developer-tools/dist/c9dec22105ad9330c811599b8b6464f8.woff +0 -0
  24. data/developer-tools/dist/ca279c55a51ab2641c4712a333633581.gif +0 -0
  25. data/developer-tools/dist/client.js +5152 -0
  26. data/developer-tools/dist/f5b27137d3f5e9b1d91b16b37386dd03.gif +0 -0
  27. data/developer-tools/dist/f99a231ed57ee113b50b1c3e9f9fcdc3.svg +399 -0
  28. data/developer-tools/dist/index.html +18 -0
  29. data/developer-tools/dist/inspector.js +38432 -0
  30. data/developer-tools/dist/jquery.min.js +9190 -0
  31. data/developer-tools/package.json +39 -0
  32. data/developer-tools/server.js +14 -0
  33. data/developer-tools/src/client.coffee +21 -0
  34. data/developer-tools/src/client/collection.coffee +14 -0
  35. data/developer-tools/src/client/model.coffee +11 -0
  36. data/developer-tools/src/client/resource.coffee +132 -0
  37. data/{app/controllers/.keep → developer-tools/src/client/runner.coffee} +0 -0
  38. data/developer-tools/src/dependencies.coffee +7 -0
  39. data/developer-tools/src/inspector.cjsx +49 -0
  40. data/developer-tools/src/inspector/models/interface_collection.coffee +31 -0
  41. data/developer-tools/src/inspector/pages/index.cjsx +31 -0
  42. data/developer-tools/src/inspector/pages/resources.cjsx +5 -0
  43. data/developer-tools/src/inspector/views/grid_sort.cjsx +23 -0
  44. data/developer-tools/src/inspector/views/icon_heading.cjsx +15 -0
  45. data/developer-tools/src/inspector/views/resource_card.cjsx +34 -0
  46. data/developer-tools/src/inspector/views/sidebar.cjsx +12 -0
  47. data/developer-tools/src/inspector/views/toolbar.cjsx +17 -0
  48. data/developer-tools/src/styles/index.scss +136 -0
  49. data/developer-tools/src/styles/views.scss +13 -0
  50. data/developer-tools/src/util.coffee +48 -0
  51. data/developer-tools/webpack.config.js +56 -0
  52. data/developer-tools/webpack.hot.config.js +65 -0
  53. data/lib/smooth.rb +209 -28
  54. data/lib/smooth/active_record/adapter.rb +24 -0
  55. data/lib/smooth/api.rb +272 -18
  56. data/lib/smooth/api/policy.rb +2 -2
  57. data/lib/smooth/api/tracking.rb +4 -4
  58. data/lib/smooth/application.rb +66 -0
  59. data/lib/smooth/cache.rb +1 -1
  60. data/lib/smooth/command.rb +267 -18
  61. data/lib/smooth/command/async_worker.rb +27 -0
  62. data/lib/smooth/command/instrumented.rb +6 -4
  63. data/lib/smooth/command/run_proxy.rb +21 -0
  64. data/lib/smooth/configuration.rb +63 -8
  65. data/lib/smooth/documentation.rb +3 -6
  66. data/lib/smooth/dsl.rb +1 -36
  67. data/lib/smooth/dsl_adapter.rb +34 -0
  68. data/lib/smooth/event.rb +8 -4
  69. data/lib/smooth/event/proxy.rb +9 -0
  70. data/lib/smooth/event/relay.rb +38 -0
  71. data/lib/smooth/example.rb +1 -1
  72. data/lib/smooth/ext/core.rb +16 -0
  73. data/lib/smooth/model_adapter.rb +31 -0
  74. data/lib/smooth/query.rb +143 -13
  75. data/lib/smooth/resource.rb +227 -52
  76. data/lib/smooth/resource/router.rb +217 -0
  77. data/lib/smooth/resource/templating.rb +62 -0
  78. data/lib/smooth/resource/tracking.rb +1 -1
  79. data/lib/smooth/response.rb +73 -0
  80. data/lib/smooth/serializer.rb +102 -11
  81. data/lib/smooth/user_adapter.rb +83 -0
  82. data/lib/smooth/util.rb +17 -0
  83. data/lib/smooth/version.rb +1 -1
  84. data/smooth.gemspec +6 -2
  85. data/spec/acceptance/books_routes_spec.rb +50 -0
  86. data/spec/acceptance/embedded_relationships_spec.rb +26 -0
  87. data/spec/dummy/app/apis/application_api.rb +8 -3
  88. data/spec/dummy/app/commands/create_book.rb +5 -0
  89. data/spec/dummy/app/models/book.rb +1 -0
  90. data/spec/dummy/app/models/library.rb +2 -0
  91. data/spec/dummy/app/models/user.rb +2 -0
  92. data/spec/dummy/app/queries/book_query.rb +13 -0
  93. data/spec/dummy/app/resources/{books.rb → books_definition.rb} +37 -12
  94. data/spec/dummy/db/migrate/20140824215902_create_users.rb +10 -0
  95. data/spec/dummy/db/migrate/20140826193259_create_libraries.rb +10 -0
  96. data/spec/dummy/db/schema.rb +8 -1
  97. data/spec/lib/smooth/api/async_spec.rb +21 -0
  98. data/spec/lib/smooth/api_spec.rb +8 -0
  99. data/spec/lib/smooth/command_spec.rb +87 -6
  100. data/spec/lib/smooth/configuration_spec.rb +4 -0
  101. data/spec/lib/smooth/event/relay_spec.rb +33 -0
  102. data/spec/lib/smooth/event_spec.rb +5 -8
  103. data/spec/lib/smooth/query_spec.rb +42 -0
  104. data/spec/lib/smooth/resource/router_spec.rb +14 -0
  105. data/spec/lib/smooth/resource_spec.rb +33 -1
  106. data/spec/lib/smooth/serializer_spec.rb +20 -0
  107. data/spec/lib/smooth/templating_spec.rb +23 -0
  108. data/spec/lib/smooth/util_spec.rb +22 -0
  109. data/spec/spec_helper.rb +1 -1
  110. metadata +151 -17
  111. data/app/helpers/.keep +0 -0
  112. data/app/mailers/.keep +0 -0
  113. data/app/models/.keep +0 -0
  114. data/app/views/.keep +0 -0
  115. data/spec/dummy/db/development.sqlite3 +0 -0
  116. data/spec/dummy/db/test.sqlite3 +0 -0
@@ -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 description, *args
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 = self._inline_description && self._inline_description.dup
23
+ val = _inline_description && _inline_description.dup
27
24
  self._inline_description = nil
28
25
  val
29
26
  end
@@ -1,36 +1 @@
1
- module Smooth
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
@@ -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 *args, &block
16
+ def track_event(*args, &_block)
15
17
  Smooth::Event.provider.send(:instrument, *args)
16
18
  end
17
19
 
18
- def subscribe_to event_name, &block
20
+ def subscribe_to(event_name, aggregator = nil, &block)
19
21
  Smooth::Event.provider.subscribe(event_name) do |*args|
20
- block.call(Smooth::Event.new(*args), event_name)
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,9 @@
1
+ class Smooth::Event::Proxy
2
+ def self.on(*args, &block)
3
+ Smooth::Event.send(:subscribe_to, *args, &block)
4
+ end
5
+
6
+ def self.trigger(*args, &block)
7
+ Smooth::Event.send(:track_event, *args, &block)
8
+ end
9
+ 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
@@ -1,6 +1,6 @@
1
1
  module Smooth
2
2
  class Example
3
- def self.configure example_description, options, resource=nil
3
+ def self.configure(_example_description, _options, _resource = nil)
4
4
  end
5
5
  end
6
6
  end
@@ -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
@@ -1,15 +1,80 @@
1
1
  module Smooth
2
- class Query
2
+ class Query < Smooth::Command
3
3
  include Smooth::Documentation
4
4
 
5
- class_attribute :query_config
6
- self.query_config = Hashie::Mash.new(base:{})
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
- def self.configure options, resource=nil
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(options, resource)
75
+ klass = define_or_open(dsl_config_object, resource)
11
76
 
12
- Array(options.blocks).each do |blk|
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 == "Default"
89
+ name = nil if name == 'Default'
90
+
91
+ klass = "#{ resource.model_class }#{ name }".singularize + 'Query'
25
92
 
26
- klass = "#{ resource_name }#{ name }".singularize + "Query"
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
- Object.const_set(klass, Class.new(base))
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 *args, &block
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 self.params *args, &block
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 name, &block
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
@@ -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 :_queries,
11
- :_commands,
12
- :_serializers,
13
- :_routes,
14
- :_examples
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
- def initialize(resource_name, options={}, &block)
17
- @resource_name = resource_name
18
- @options = options
47
+ load!
48
+ end
19
49
 
20
- @_serializers = {}.to_mash
21
- @_commands = {}.to_mash
22
- @_queries = {}.to_mash
23
- @_routes = {}.to_mash
24
- @_examples = {}.to_mash
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
- @loaded = false
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
- instance_eval(&block) if block_given?
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
- load!
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 fetch_config object_type, object_key
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 && source.fetch(object_key.to_sym)
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 object_type, object_key
131
+ def fetch(object_type, object_key)
43
132
  source = instance_variable_get("@#{ object_type }s") rescue nil
44
- source && source.fetch(object_key.to_sym)
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 *opts
153
+ def apply_options(*opts)
64
154
  @options.send(:merge!, *opts)
65
155
  end
66
156
 
67
- def serializer serializer_name="Default", *args, &block
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
- description = options.fetch(:description) do
75
- args.first || inline_description
76
- end
190
+ provided_description = options.fetch(:description, inline_description)
77
191
 
78
- config = _serializers[serializer_name.to_sym] ||= Hashie::Mash.new(options: {}, name: serializer_name, blocks: [block].compact)
79
- config.description = description unless description.nil?
192
+ describe_object(:serializer, serializer_name.downcase, provided_description) unless provided_description.empty?
80
193
 
81
- config
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 command_name, *args, &block
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
- description = options.fetch(:description) do
92
- args.first || inline_description
93
- end
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
- config = _commands[command_name.to_sym] ||= Hashie::Mash.new(options: {}, name: command_name, blocks: [block].compact)
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 = description unless description.nil?
220
+ config.description = provided_description unless provided_description.nil?
99
221
 
100
222
  config
101
223
  end
102
224
 
103
- def query query_name="Default", *args, &block
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
- description = options.fetch(:description) do
111
- args.first || inline_description
112
- end
232
+ provided_description = options.fetch(:description, inline_description)
113
233
 
114
- config = _queries[query_name.to_sym] ||= Hashie::Mash.new(options: {}, name: query_name, blocks: [block].compact)
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 = description unless description.nil?
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 @routes if !block_given?
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 options={}, &block
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.inject({}.to_mash) do |memo, p|
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.inject({}.to_mash) do |memo, p|
147
- ref, cfg = p
148
- memo[cfg.name] = Smooth::Serializer.configure(cfg, resource)
149
- memo
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.inject({}.to_mash) do |memo, p|
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'