smooth 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
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'