praxis 2.0.pre.8 → 2.0.pre.13

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 (242) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +1 -3
  5. data/CHANGELOG.md +33 -0
  6. data/TODO.md +1 -4
  7. data/bin/praxis +67 -12
  8. data/lib/praxis.rb +10 -3
  9. data/lib/praxis/action_definition.rb +15 -13
  10. data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
  11. data/lib/praxis/api_general_info.rb +1 -1
  12. data/lib/praxis/application.rb +6 -2
  13. data/lib/praxis/blueprint.rb +357 -0
  14. data/lib/praxis/bootloader.rb +9 -3
  15. data/lib/praxis/bootloader_stages/environment.rb +16 -13
  16. data/lib/praxis/collection.rb +1 -11
  17. data/lib/praxis/config_hash.rb +44 -0
  18. data/lib/praxis/docs/{openapi → open_api}/info_object.rb +18 -10
  19. data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
  20. data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
  21. data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +0 -0
  22. data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +0 -0
  23. data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
  24. data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
  25. data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
  26. data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
  27. data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
  28. data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
  29. data/lib/praxis/docs/open_api_generator.rb +91 -6
  30. data/lib/praxis/endpoint_definition.rb +273 -0
  31. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +182 -58
  32. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
  33. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +47 -56
  34. data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +153 -0
  35. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
  36. data/lib/praxis/extensions/field_expansion.rb +3 -36
  37. data/lib/praxis/extensions/pagination.rb +5 -32
  38. data/lib/praxis/extensions/pagination/ordering_params.rb +1 -1
  39. data/lib/praxis/extensions/pagination/pagination_params.rb +6 -4
  40. data/lib/praxis/field_expander.rb +90 -0
  41. data/lib/praxis/finalizable.rb +34 -0
  42. data/lib/praxis/mapper/active_model_compat.rb +4 -0
  43. data/lib/praxis/mapper/resource.rb +18 -2
  44. data/lib/praxis/mapper/selector_generator.rb +2 -1
  45. data/lib/praxis/mapper/sequel_compat.rb +7 -0
  46. data/lib/praxis/media_type.rb +3 -68
  47. data/lib/praxis/plugin_concern.rb +1 -1
  48. data/lib/praxis/plugins/mapper_plugin.rb +24 -15
  49. data/lib/praxis/plugins/pagination_plugin.rb +34 -4
  50. data/lib/praxis/renderer.rb +88 -0
  51. data/lib/praxis/request.rb +1 -1
  52. data/lib/praxis/resource_definition.rb +2 -311
  53. data/lib/praxis/response_definition.rb +2 -10
  54. data/lib/praxis/response_template.rb +3 -3
  55. data/lib/praxis/router.rb +2 -2
  56. data/lib/praxis/routing_config.rb +1 -1
  57. data/lib/praxis/tasks/api_docs.rb +17 -64
  58. data/lib/praxis/tasks/routes.rb +1 -1
  59. data/lib/praxis/types/media_type_common.rb +1 -11
  60. data/lib/praxis/version.rb +1 -1
  61. data/praxis.gemspec +0 -1
  62. data/spec/functional_spec.rb +5 -9
  63. data/spec/praxis/action_definition_spec.rb +12 -20
  64. data/spec/praxis/blueprint_spec.rb +373 -0
  65. data/spec/praxis/bootloader_spec.rb +10 -2
  66. data/spec/praxis/collection_spec.rb +0 -13
  67. data/spec/praxis/config_hash_spec.rb +64 -0
  68. data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +37 -64
  69. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +249 -168
  70. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
  71. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +190 -8
  72. data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +140 -0
  73. data/spec/praxis/extensions/field_expansion_spec.rb +5 -24
  74. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
  75. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
  76. data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
  77. data/spec/praxis/field_expander_spec.rb +149 -0
  78. data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
  79. data/spec/praxis/media_type_identifier_spec.rb +5 -4
  80. data/spec/praxis/media_type_spec.rb +4 -93
  81. data/spec/praxis/renderer_spec.rb +188 -0
  82. data/spec/praxis/response_definition_spec.rb +0 -31
  83. data/spec/praxis/response_spec.rb +1 -1
  84. data/spec/praxis/router_spec.rb +8 -8
  85. data/spec/praxis/routing_config_spec.rb +3 -3
  86. data/spec/spec_app/app/controllers/instances.rb +13 -7
  87. data/spec/spec_app/design/media_types/instance.rb +1 -19
  88. data/spec/spec_app/design/media_types/volume.rb +1 -1
  89. data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
  90. data/spec/spec_app/design/resources/instances.rb +5 -8
  91. data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
  92. data/spec/spec_app/design/resources/volumes.rb +1 -1
  93. data/spec/support/spec_authorization_plugin.rb +1 -1
  94. data/spec/support/spec_blueprints.rb +72 -0
  95. data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
  96. data/spec/support/spec_media_types.rb +6 -26
  97. data/tasks/thor/app.rb +8 -34
  98. data/tasks/thor/example.rb +51 -285
  99. data/tasks/thor/model.rb +40 -0
  100. data/tasks/thor/scaffold.rb +117 -0
  101. data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
  102. data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
  103. data/tasks/thor/templates/generator/empty_app/README.md +1 -1
  104. data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
  105. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
  106. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
  107. data/tasks/thor/templates/generator/empty_app/config/environment.rb +26 -17
  108. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
  109. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
  110. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
  111. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
  112. data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
  113. data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
  114. data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
  115. data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
  116. data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
  117. data/tasks/thor/templates/generator/example_app/Rakefile +61 -0
  118. data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
  119. data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
  120. data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
  121. data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +11 -0
  122. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +25 -0
  123. data/tasks/thor/templates/generator/example_app/config.ru +30 -0
  124. data/tasks/thor/templates/generator/example_app/config/environment.rb +41 -0
  125. data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +12 -0
  126. data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
  127. data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
  128. data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
  129. data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +21 -0
  130. data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +20 -0
  131. data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
  132. data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
  133. data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
  134. data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
  135. data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
  136. data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
  137. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
  138. data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
  139. data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
  140. metadata +64 -136
  141. data/lib/api_browser/.bowerrc +0 -3
  142. data/lib/api_browser/.editorconfig +0 -21
  143. data/lib/api_browser/Gruntfile.js +0 -581
  144. data/lib/api_browser/app/index.html +0 -59
  145. data/lib/api_browser/app/js/app.js +0 -48
  146. data/lib/api_browser/app/js/controllers/action.js +0 -47
  147. data/lib/api_browser/app/js/controllers/controller.js +0 -10
  148. data/lib/api_browser/app/js/controllers/menu.js +0 -93
  149. data/lib/api_browser/app/js/controllers/trait.js +0 -10
  150. data/lib/api_browser/app/js/controllers/type.js +0 -24
  151. data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
  152. data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
  153. data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
  154. data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
  155. data/lib/api_browser/app/js/directives/highlight.js +0 -14
  156. data/lib/api_browser/app/js/directives/menu_item.js +0 -59
  157. data/lib/api_browser/app/js/directives/no_container.js +0 -8
  158. data/lib/api_browser/app/js/directives/readable_list.js +0 -87
  159. data/lib/api_browser/app/js/directives/request_examples.js +0 -31
  160. data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
  161. data/lib/api_browser/app/js/directives/url.js +0 -15
  162. data/lib/api_browser/app/js/factories/Configuration.js +0 -12
  163. data/lib/api_browser/app/js/factories/Documentation.js +0 -61
  164. data/lib/api_browser/app/js/factories/Example.js +0 -51
  165. data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
  166. data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
  167. data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
  168. data/lib/api_browser/app/js/factories/template_for.js +0 -128
  169. data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
  170. data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
  171. data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
  172. data/lib/api_browser/app/js/filters/header_info.js +0 -9
  173. data/lib/api_browser/app/js/filters/is_empty.js +0 -8
  174. data/lib/api_browser/app/js/filters/markdown.js +0 -6
  175. data/lib/api_browser/app/js/filters/resource_name.js +0 -5
  176. data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
  177. data/lib/api_browser/app/sass/modules/_body.scss +0 -40
  178. data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
  179. data/lib/api_browser/app/sass/modules/_header.scss +0 -10
  180. data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
  181. data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
  182. data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
  183. data/lib/api_browser/app/sass/modules/_table.scss +0 -13
  184. data/lib/api_browser/app/sass/praxis.scss +0 -70
  185. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
  186. data/lib/api_browser/app/views/action.html +0 -97
  187. data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
  188. data/lib/api_browser/app/views/controller.html +0 -55
  189. data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
  190. data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
  191. data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
  192. data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
  193. data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
  194. data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
  195. data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
  196. data/lib/api_browser/app/views/directives/menu_item.html +0 -8
  197. data/lib/api_browser/app/views/directives/url.html +0 -3
  198. data/lib/api_browser/app/views/examples/general.html +0 -26
  199. data/lib/api_browser/app/views/home.html +0 -5
  200. data/lib/api_browser/app/views/layout.html +0 -8
  201. data/lib/api_browser/app/views/menu.html +0 -42
  202. data/lib/api_browser/app/views/navbar.html +0 -9
  203. data/lib/api_browser/app/views/trait.html +0 -13
  204. data/lib/api_browser/app/views/type.html +0 -6
  205. data/lib/api_browser/app/views/type/details.html +0 -33
  206. data/lib/api_browser/app/views/types/embedded/array.html +0 -2
  207. data/lib/api_browser/app/views/types/embedded/default.html +0 -12
  208. data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
  209. data/lib/api_browser/app/views/types/embedded/links.html +0 -11
  210. data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
  211. data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
  212. data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
  213. data/lib/api_browser/app/views/types/label/link.html +0 -1
  214. data/lib/api_browser/app/views/types/label/primitive.html +0 -1
  215. data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
  216. data/lib/api_browser/app/views/types/label/type.html +0 -1
  217. data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
  218. data/lib/api_browser/app/views/types/main/array.html +0 -22
  219. data/lib/api_browser/app/views/types/main/default.html +0 -23
  220. data/lib/api_browser/app/views/types/main/hash.html +0 -23
  221. data/lib/api_browser/app/views/types/standalone/array.html +0 -3
  222. data/lib/api_browser/app/views/types/standalone/default.html +0 -18
  223. data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
  224. data/lib/api_browser/bower_template.json +0 -41
  225. data/lib/api_browser/package-lock.json +0 -7110
  226. data/lib/api_browser/package.json +0 -43
  227. data/lib/praxis/docs/generator.rb +0 -243
  228. data/lib/praxis/docs/link_builder.rb +0 -30
  229. data/lib/praxis/links.rb +0 -135
  230. data/lib/praxis/types/multipart.rb +0 -109
  231. data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
  232. data/spec/api_browser/factories/configuration_spec.js +0 -32
  233. data/spec/api_browser/factories/documentation_spec.js +0 -100
  234. data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
  235. data/spec/api_browser/factories/template_for_spec.js +0 -67
  236. data/spec/api_browser/filters/attribute_name_spec.js +0 -23
  237. data/spec/praxis/types/multipart_spec.rb +0 -112
  238. data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
  239. data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
  240. data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
  241. data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
  242. data/tasks/thor/templates/generator/empty_app/docs/styles.scss +0 -3
@@ -11,7 +11,7 @@ namespace :praxis do
11
11
  ]
12
12
 
13
13
  rows = []
14
- Praxis::Application.instance.resource_definitions.each do |resource_definition|
14
+ Praxis::Application.instance.endpoint_definitions.each do |resource_definition|
15
15
  resource_definition.actions.each do |name, action|
16
16
  method = begin
17
17
  m = resource_definition.controller.instance_method(name)
@@ -6,14 +6,6 @@ module Praxis
6
6
  extend ::ActiveSupport::Concern
7
7
 
8
8
  module ClassMethods
9
- def describe(shallow = false, **opts)
10
- hash = super
11
- unless shallow
12
- hash.merge!(identifier: @identifier.to_s, description: @description, display_name: self.display_name)
13
- end
14
- hash
15
- end
16
-
17
9
  def as_json_schema(**args)
18
10
  the_type = @attribute && @attribute.type || member_type
19
11
  the_type.as_json_schema(args)
@@ -38,9 +30,7 @@ module Praxis
38
30
 
39
31
  # Get or set the identifier of this media type.
40
32
  #
41
- # @deprecated this method is not deprecated, but its return type will change to MediaTypeIdentifier in Praxis 1.0
42
- #
43
- # @return [String] the string-representation of this type's identifier
33
+ # @return [MediaTypeIdentifier] the string-representation of this type's identifier
44
34
  def identifier(identifier=nil)
45
35
  return @identifier unless identifier
46
36
  @identifier = MediaTypeIdentifier.load(identifier)
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- VERSION = '2.0.pre.8'
2
+ VERSION = '2.0.pre.13'
3
3
  end
data/praxis.gemspec CHANGED
@@ -24,7 +24,6 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency 'mustermann', '>=1.1', '<=2'
25
25
  spec.add_dependency 'activesupport', '>= 3'
26
26
  spec.add_dependency 'mime', '~> 0'
27
- spec.add_dependency 'praxis-blueprints', '>= 3.5'
28
27
  spec.add_dependency 'attributor', '>= 5.5'
29
28
  spec.add_dependency 'thor'
30
29
  spec.add_dependency 'terminal-table', '~> 1.4'
@@ -146,7 +146,7 @@ describe 'Functional specs' do
146
146
  context 'bulk_create multipart' do
147
147
 
148
148
  let(:instance) { Instance.example }
149
- let(:instance_json) { JSON.pretty_generate(instance.render(view: :create)) }
149
+ let(:instance_json) { JSON.pretty_generate(instance.render(fields: {id: true, name: true})) }
150
150
 
151
151
  let(:form) do
152
152
  form_data = MIME::Multipart::FormData.new
@@ -174,7 +174,7 @@ describe 'Functional specs' do
174
174
 
175
175
  response_instance = JSON.parse(instance_part.body)
176
176
  expect(response_instance["key"]).to eq(instance.id)
177
- expect(response_instance["value"].values).to eq(instance.render(view: :create).values)
177
+ expect(response_instance["value"].values).to eq(instance.render(fields: {id: true, name: true}).values)
178
178
  end
179
179
  end
180
180
 
@@ -205,12 +205,10 @@ describe 'Functional specs' do
205
205
  its(['destination_path']) { should eq '/etc/defaults' }
206
206
 
207
207
  context 'response["file"]' do
208
- subject(:file) { response['file'] }
209
-
210
208
  its(['filename']) { should eq('docker') }
211
209
  its(['type']) { should eq('text/plain') }
212
210
  its(['name']) { should eq('file') }
213
- its(['tempfile']) { should eq('DOCKER_HOST=tcp://127.0.0.1:2375') }
211
+ its(['contents']) { should eq('DOCKER_HOST=tcp://127.0.0.1:2375') }
214
212
  end
215
213
  end
216
214
 
@@ -231,7 +229,7 @@ describe 'Functional specs' do
231
229
  response = JSON.parse(last_response.body)
232
230
 
233
231
  expect(response['name']).to eq('ValidationError')
234
- expect(response['errors']).to eq(["Attribute $.payload.key(\"destination_path\") is required"])
232
+ expect(response['errors']).to eq(["Attribute $.payload.destination_path is required"])
235
233
  end
236
234
 
237
235
  end
@@ -259,7 +257,7 @@ describe 'Functional specs' do
259
257
  before do
260
258
  post '/api/clouds/1/instances/2/files?api_version=1.0', body, 'CONTENT_TYPE' => content_type, 'global_session' => session
261
259
  end
262
- its(:keys){ should eq(['destination_path','file','options'])}
260
+ its(:keys){ should eq(['destination_path','name','filename','type','contents','options'])}
263
261
  its(['options']){ should eq({"extra_thing"=>"I am extra"})}
264
262
  end
265
263
 
@@ -334,8 +332,6 @@ describe 'Functional specs' do
334
332
  let(:content_type){ 'application/json' }
335
333
  it 'can terminate instances with POST' do
336
334
  post '/api/clouds/23/instances/1/terminate?api_version=1.0', nil, 'CONTENT_TYPE' => content_type, 'global_session' => session
337
- puts last_response.body
338
- #binding.pry
339
335
  expect(last_response.status).to eq(200)
340
336
  end
341
337
  it 'can terminate instances with DELETE' do
@@ -8,14 +8,14 @@ describe Praxis::ActionDefinition do
8
8
  attribute :one, String
9
9
  attribute :two, Integer
10
10
  end
11
- view :default do
11
+ default_fieldset do
12
12
  attribute :one
13
13
  end
14
14
  end
15
15
 
16
- let(:resource_definition) do
16
+ let(:endpoint_definition) do
17
17
  Class.new do
18
- include Praxis::ResourceDefinition
18
+ include Praxis::EndpointDefinition
19
19
 
20
20
  def self.name
21
21
  'FooBar'
@@ -43,7 +43,7 @@ describe Praxis::ActionDefinition do
43
43
  headers headers if headers
44
44
  end
45
45
  end
46
- Praxis::ActionDefinition.new(:foo, resource_definition) do
46
+ Praxis::ActionDefinition.new(:foo, endpoint_definition) do
47
47
  routing { get '/:one' }
48
48
  payload { attribute :two, String }
49
49
  headers { header "X_REQUESTED_WITH", 'XMLHttpRequest' }
@@ -54,7 +54,7 @@ describe Praxis::ActionDefinition do
54
54
 
55
55
  context '#initialize' do
56
56
  its('name') { should eq :foo }
57
- its('resource_definition') { should be resource_definition }
57
+ its('endpoint_definition') { should be endpoint_definition }
58
58
  its('params.attributes') { should have_key :one }
59
59
  its('params.attributes') { should have_key :inherited }
60
60
  its('payload.attributes') { should have_key :two }
@@ -78,17 +78,9 @@ describe Praxis::ActionDefinition do
78
78
  it { should include :created }
79
79
  end
80
80
 
81
- describe '#allowed_responses' do
82
- it 'has some tests after we stop using ApiDefinition.instance'
83
- end
84
-
85
- describe '#use' do
86
- it 'has some tests after we stop using ApiDefinition.instance'
87
- end
88
-
89
81
  describe 'when a trait is used' do
90
82
  subject(:action) do
91
- Praxis::ActionDefinition.new(:bar, resource_definition) do
83
+ Praxis::ActionDefinition.new(:bar, endpoint_definition) do
92
84
  trait :test
93
85
  routing { get '/:one' }
94
86
  params { attribute :one, String }
@@ -277,11 +269,11 @@ describe Praxis::ActionDefinition do
277
269
  end
278
270
 
279
271
  context 'href generation' do
280
- let(:resource_definition) { ApiResources::Instances }
281
- subject(:action) { resource_definition.actions[:show] }
272
+ let(:endpoint_definition) { ApiResources::Instances }
273
+ subject(:action) { endpoint_definition.actions[:show] }
282
274
 
283
275
  it 'works' do
284
- expansion = action.route.path.expand(cloud_id:232, id: 2)
276
+ expansion = action.route.path.expand(cloud_id: '232', id: '2')
285
277
  expect(expansion).to eq "/api/clouds/232/instances/2"
286
278
  end
287
279
 
@@ -332,9 +324,9 @@ describe Praxis::ActionDefinition do
332
324
 
333
325
  context 'where the action overrides a base_param' do
334
326
 
335
- let(:resource_definition) do
327
+ let(:endpoint_definition) do
336
328
  Class.new do
337
- include Praxis::ResourceDefinition
329
+ include Praxis::EndpointDefinition
338
330
 
339
331
  def self.name
340
332
  'FooBar'
@@ -349,7 +341,7 @@ describe Praxis::ActionDefinition do
349
341
  end
350
342
 
351
343
  let(:action) do
352
- Praxis::ActionDefinition.new(:foo, resource_definition) do
344
+ Praxis::ActionDefinition.new(:foo, endpoint_definition) do
353
345
  routing { get '' }
354
346
  params { attribute :app_name, Integer }
355
347
  end
@@ -0,0 +1,373 @@
1
+ # frozen_string_literal: true
2
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3
+
4
+ describe Praxis::Blueprint do
5
+ subject(:blueprint_class) { PersonBlueprint }
6
+
7
+ its(:family) { should eq('hash') }
8
+
9
+ context 'deterministic examples' do
10
+ it 'works' do
11
+ person_1 = PersonBlueprint.example('person 1')
12
+ person_2 = PersonBlueprint.example('person 1')
13
+
14
+ expect(person_1.name).to eq(person_2.name)
15
+ expect(person_1.address.name).to eq(person_2.address.name)
16
+ end
17
+ end
18
+
19
+ context 'implicit default_fieldset (when not defined in the blueprint)' do
20
+ subject(:default_fieldset) { AddressBlueprint.default_fieldset }
21
+
22
+ it { should_not be(nil) }
23
+ it 'contains all attributes' do
24
+ simple_attributes = [:id, :name, :street, :state]
25
+ expect(default_fieldset.keys).to match_array(simple_attributes)
26
+ # Should not have blueprint-derived attributes (or collections of them)
27
+ expect(default_fieldset.keys).to_not include( AddressBlueprint.attributes.keys - simple_attributes )
28
+ end
29
+ end
30
+
31
+ context 'creating a new Blueprint class' do
32
+ subject!(:blueprint_class) do
33
+ Class.new(Praxis::Blueprint) do
34
+ domain_model Hash
35
+ attributes do
36
+ attribute :id, Integer
37
+ end
38
+ end
39
+ end
40
+
41
+ its(:finalized?) { should be(false) }
42
+ its(:domain_model) { should be(Hash) }
43
+
44
+ context '.finalize on Praxis::Blueprint' do
45
+ before do
46
+ expect(blueprint_class).to receive(:_finalize!).and_call_original
47
+ Praxis::Blueprint.finalize!
48
+ end
49
+
50
+ its(:finalized?) { should be(true) }
51
+ end
52
+
53
+ context '.finalize on that subclass' do
54
+ before do
55
+ expect(blueprint_class).to receive(:_finalize!).and_call_original
56
+ blueprint_class.finalize!
57
+ end
58
+
59
+ its(:finalized?) { should be(true) }
60
+ end
61
+ end
62
+
63
+ context 'creating a base abstract Blueprint class without attributes' do
64
+ subject!(:blueprint_class) do
65
+ Class.new(Praxis::Blueprint)
66
+ end
67
+
68
+ it 'skips attribute definition' do
69
+ expect(blueprint_class).to receive(:_finalize!).and_call_original
70
+ expect(blueprint_class).to_not receive(:define_attribute)
71
+ blueprint_class.finalize!
72
+ expect(blueprint_class.finalized?).to be(true)
73
+ end
74
+ end
75
+
76
+ it 'has an inner Struct class for the attributes' do
77
+ expect(blueprint_class.attribute.type).to be blueprint_class::Struct
78
+ end
79
+
80
+
81
+ context 'an instance' do
82
+ shared_examples 'a blueprint instance' do
83
+ let(:expected_name) { blueprint_instance.name }
84
+
85
+ context '#render' do
86
+ subject(:output) { blueprint_instance.render }
87
+
88
+ it { should have_key(:name) }
89
+ it 'has the right values' do
90
+ expect(subject[:name]).to eq(expected_name)
91
+ end
92
+ end
93
+
94
+ context 'validation' do
95
+ subject(:errors) { blueprint_class.validate(blueprint_instance) }
96
+ it { should be_empty }
97
+ end
98
+ end
99
+
100
+ context 'from Blueprint.example' do
101
+ subject(:blueprint_instance) do
102
+ blueprint_class.example('ExamplePersonBlueprint',
103
+ address: nil,
104
+ prior_addresses: [],
105
+ work_address: nil,
106
+ myself: nil,
107
+ friends: []
108
+ )
109
+ end
110
+ it_behaves_like 'a blueprint instance'
111
+ end
112
+
113
+ context 'wrapping an object' do
114
+ let(:data) do
115
+ {
116
+ name: 'Bob',
117
+ full_name: FullName.example,
118
+ address: nil,
119
+ email: 'bob@example.com',
120
+ aliases: [],
121
+ prior_addresses: [],
122
+ parents: { father: Randgen.first_name, mother: Randgen.first_name },
123
+ href: 'www.example.com',
124
+ alive: true
125
+ }
126
+ end
127
+
128
+ let(:resource) { blueprint_class.load(data).object }
129
+
130
+ subject(:blueprint_instance) { blueprint_class.new(resource) }
131
+
132
+ it_behaves_like 'a blueprint instance'
133
+
134
+ context 'creating additional blueprint instances from that object' do
135
+ subject(:additional_instance) { blueprint_class.new(resource) }
136
+
137
+ context 'with caching enabled' do
138
+ around do |example|
139
+ Praxis::Blueprint.caching_enabled = true
140
+ Praxis::Blueprint.cache = Hash.new { |h, k| h[k] = {} }
141
+ example.run
142
+
143
+ Praxis::Blueprint.caching_enabled = false
144
+ Praxis::Blueprint.cache = nil
145
+ end
146
+
147
+ it 'uses the cache to memoize instance creation' do
148
+ expect(additional_instance).to be(additional_instance)
149
+ expect(blueprint_class.cache).to have_key(resource)
150
+ expect(blueprint_class.cache[resource]).to be(blueprint_instance)
151
+ end
152
+ end
153
+
154
+ context 'with caching disabled' do
155
+ it { should_not be blueprint_instance }
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ context '.validate' do
162
+ let(:hash) { { name: 'bob' } }
163
+ let(:person) { PersonBlueprint.load(hash) }
164
+ subject(:errors) { person.validate }
165
+
166
+ context 'that is valid' do
167
+ it { should be_empty }
168
+ end
169
+
170
+ context 'with invalid sub-attribute' do
171
+ let(:hash) { { name: 'bob', address: { state: 'ME' } } }
172
+
173
+ it { should have(1).item }
174
+ its(:first) { should =~ /Attribute \$.address.state/ }
175
+ end
176
+
177
+ context 'for objects of the wrong type' do
178
+ it 'raises an error' do
179
+ expect do
180
+ PersonBlueprint.validate(Object.new)
181
+ end.to raise_error(ArgumentError, /Error validating .* as PersonBlueprint for an object of type Object/)
182
+ end
183
+ end
184
+ end
185
+
186
+ context '.load' do
187
+ let(:hash) do
188
+ {
189
+ name: 'Bob',
190
+ full_name: { first: 'Robert', last: 'Robertson' },
191
+ address: { street: 'main', state: 'OR' }
192
+ }
193
+ end
194
+ subject(:person) { PersonBlueprint.load(hash) }
195
+
196
+ it { should be_kind_of(PersonBlueprint) }
197
+
198
+ context 'recursively loading sub-attributes' do
199
+ context 'for a Blueprint' do
200
+ subject(:address) { person.address }
201
+ it { should be_kind_of(AddressBlueprint) }
202
+ end
203
+ context 'for an Attributor::Model' do
204
+ subject(:full_name) { person.full_name }
205
+ it { should be_kind_of(FullName) }
206
+ end
207
+ end
208
+ end
209
+
210
+ context 'with a provided :reference option on attributes' do
211
+ context 'that does not match the value set on the class' do
212
+ subject(:mismatched_reference) do
213
+ Class.new(Praxis::Blueprint) do
214
+ self.reference = Class.new(Praxis::Blueprint)
215
+ attributes(reference: Class.new(Praxis::Blueprint)) {}
216
+ end
217
+ end
218
+
219
+ it 'should raise an error' do
220
+ expect do
221
+ mismatched_reference.attributes
222
+ end.to raise_error(/Reference mismatch/)
223
+ end
224
+ end
225
+ end
226
+
227
+ context '.example' do
228
+ context 'with some attribute values provided' do
229
+ let(:name) { 'Sir Bobbert' }
230
+ subject(:person) { PersonBlueprint.example(name: name) }
231
+ its(:name) { should eq(name) }
232
+ end
233
+ end
234
+
235
+ context '.render' do
236
+ let(:person) { PersonBlueprint.example('1') }
237
+ it 'is an alias to dump' do
238
+ person.object.contents
239
+ rendered = PersonBlueprint.render(person, fields: [:name, :full_name])
240
+ dumped = PersonBlueprint.dump(person, fields: [:name, :full_name])
241
+ expect(rendered).to eq(dumped)
242
+ end
243
+ end
244
+
245
+ context '#render' do
246
+ let(:person) { PersonBlueprint.example }
247
+ let(:fields) do
248
+ {
249
+ name: true,
250
+ full_name: true,
251
+ address: {
252
+ street: true,
253
+ state: true,
254
+ },
255
+ prior_addresses: {
256
+ street: true,
257
+ state: true,
258
+ }
259
+ }
260
+ end
261
+ let(:render_opts) { {} }
262
+ subject(:output) { person.render(fields: fields, **render_opts) }
263
+
264
+ context 'without passing fields' do
265
+ it 'renders the default field set defined' do
266
+ rendered = person.render( **render_opts)
267
+ default_top_fields = PersonBlueprint.default_fieldset.keys
268
+ expect(rendered.keys).to match_array(default_top_fields)
269
+ expect(default_top_fields).to match_array([
270
+ :name,
271
+ :full_name,
272
+ :address,
273
+ :prior_addresses])
274
+ end
275
+ end
276
+ context 'with a sub-attribute that is a blueprint' do
277
+ it { should have_key(:name) }
278
+ it { should have_key(:address) }
279
+ it 'renders the sub-attribute correctly' do
280
+ expect(output[:address]).to have_key(:street)
281
+ expect(output[:address]).to have_key(:state)
282
+ end
283
+
284
+ it 'reports a dump error with the appropriate context' do
285
+ expect(person.address).to receive(:state).and_raise('Kaboom')
286
+ expect do
287
+ person.render(fields: fields, context: ['special_root'])
288
+ end.to raise_error(/Error while dumping attribute state of type AddressBlueprint for context special_root.address. Reason: .*Kaboom/)
289
+ end
290
+ end
291
+
292
+ context 'with sub-attribute that is an Attributor::Model' do
293
+ it { should have_key(:full_name) }
294
+ it 'renders the model correctly' do
295
+ expect(output[:full_name]).to be_kind_of(Hash)
296
+ expect(output[:full_name]).to have_key(:first)
297
+ expect(output[:full_name]).to have_key(:last)
298
+ end
299
+ end
300
+
301
+ context 'using the `fields` option' do
302
+ context 'as a hash' do
303
+ subject(:output) { person.render(fields: { address: { state: true } }) }
304
+ it 'should only have the address rendered' do
305
+ expect(output.keys).to eq [:address]
306
+ end
307
+ it 'address should only have state' do
308
+ expect(output[:address].keys).to eq [:state]
309
+ end
310
+ end
311
+ context 'as a simple array' do
312
+ subject(:output) { person.render(fields: [:full_name]) }
313
+ it 'accepts it as the list of top-level attributes to be rendered' do
314
+ expect(output.keys).to match_array([:full_name])
315
+ end
316
+ end
317
+ end
318
+
319
+ context 'using un-expanded fields for blueprints' do
320
+ let(:fields) do
321
+ {
322
+ name: true,
323
+ address: true, # A blueprint!
324
+ }
325
+ end
326
+ it 'should still render the blueprint sub-attribute with its default fieldset' do
327
+ address_default_top_fieldset = AddressBlueprint.default_fieldset.keys
328
+ expect(output[:address].keys).to match(address_default_top_fieldset)
329
+ end
330
+
331
+ end
332
+ end
333
+
334
+ context '.as_json_schema' do
335
+ it 'delegates to the attribute type' do
336
+ expect(PersonBlueprint.attribute.type).to receive(:as_json_schema)
337
+ PersonBlueprint.as_json_schema
338
+ end
339
+ end
340
+ context '.json_schema_type' do
341
+ it 'delegates to the attribute type' do
342
+ expect(PersonBlueprint.attribute.type).to receive(:json_schema_type)
343
+ PersonBlueprint.json_schema_type
344
+ end
345
+ end
346
+
347
+ context 'FieldsetParser' do
348
+ let(:definition_block) do
349
+ Proc.new do
350
+ attribute :one
351
+ attribute :two do
352
+ attribute :sub_two
353
+ end
354
+ end
355
+ end
356
+ subject { described_class::FieldsetParser.new(&definition_block) }
357
+
358
+ it 'parses properly' do
359
+ expect(subject.fieldset).to eq(one: true, two: { sub_two: true} )
360
+ end
361
+
362
+ context 'with attribute parameters' do
363
+ let(:definition_block) do
364
+ Proc.new do
365
+ attribute :one, view: :other
366
+ end
367
+ end
368
+ it 'complains and gives instructions if legacy view :default' do
369
+ expect{ subject.fieldset }.to raise_error(/Default fieldset definitions do not accept parameters/)
370
+ end
371
+ end
372
+ end
373
+ end