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
@@ -0,0 +1,18 @@
1
+ # Use this file to define your overall api behavior, response templates and traits.
2
+ Praxis::ApiDefinition.define do
3
+ info do
4
+ name 'example'
5
+ title 'Example API'
6
+
7
+ # Attributes for OpenAPI docs
8
+ termsOfService 'https://mysitehere.com'
9
+ contact name: 'API Info', email: 'info@mysitehere.com'
10
+ end
11
+
12
+ # Trait that when included will require a Bearer authorization header to be passed in.
13
+ trait :authorized do
14
+ headers do
15
+ key "Authorization", String, regexp: /^.*Bearer\s/, required: true
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module V1
4
+ module Endpoints
5
+ class Users
6
+ include Praxis::EndpointDefinition
7
+
8
+ media_type MediaTypes::User
9
+ version '1'
10
+
11
+ description 'Endpoints that allow the listing and manipulation of users'
12
+
13
+ action :index do
14
+ description 'List users'
15
+ routing { get '' }
16
+ params do
17
+ attribute :fields, Praxis::Types::FieldSelector.for(MediaTypes::User),
18
+ description: 'Fields with which to render the result.'
19
+ attribute :filters, Praxis::Types::FilteringParams.for(MediaTypes::User) do
20
+ filter 'uuid', using: ['=', '!=']
21
+ filter 'first_name', using: ['=', '!='], fuzzy: true
22
+ filter 'last_name', using: ['=', '!='], fuzzy: true
23
+ filter 'email', using: ['=', '!=']
24
+ end
25
+ attribute :pagination, Praxis::Types::PaginationParams.for(MediaTypes::User) do
26
+ by_fields :uuid, :first_name, :last_name
27
+ end
28
+ attribute :order, Praxis::Extensions::Pagination::OrderingParams.for(MediaTypes::User) do
29
+ by_fields :uuid, :last_name, :first_name
30
+ end
31
+ end
32
+ response :ok, media_type: Praxis::Collection.of(MediaTypes::User)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module V1
4
+ module MediaTypes
5
+ class User < Praxis::MediaType
6
+ identifier 'application/json'
7
+
8
+ domain_model 'V1::Resources::User'
9
+ description 'A user in the system'
10
+
11
+ attributes do
12
+ attribute :id, Integer
13
+ attribute :uuid, String
14
+ attribute :email, String
15
+ attribute :first_name, String
16
+ attribute :last_name, String
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,20 @@
1
+ require 'active_record'
2
+
3
+ class DatabaseHelper
4
+ # Simple DB seeding to avoid bringing in other gems like FactoryGirl etc.
5
+ # This does the job for an example seeder
6
+ def self.seed!
7
+ user_data = [
8
+ {id: 11, first_name: 'Peter', last_name: 'Praxis', uuid: 'deadbeef', email: 'peter@pan.com'},
9
+ {id: 12, first_name: 'Alice', last_name: 'Trellis', uuid: 'beefdead', email: 'alice@wonderland.com'},
10
+ {id: 13, first_name: 'Wellington', last_name: 'Lofty', uuid: 'beefbeef', email: 'well@lofty.com'},
11
+ ]
12
+ (100..199).each do |i|
13
+ user_data.push id: i, first_name: "User-#{i}", last_name: "Last-#{i}", uuid: SecureRandom.hex(16).to_s
14
+ end
15
+ user_data.each_with_index do |data, i|
16
+ ::User.create(**data)
17
+ end
18
+ puts "Database seeded."
19
+ end
20
+ end
@@ -0,0 +1,42 @@
1
+ Bundler.setup :default, :test
2
+ Bundler.require :default, :test
3
+ ENV['RACK_ENV'] = 'test'
4
+
5
+ begin
6
+ APP=Rack::Builder.parse_file(File.expand_path('../config.ru', __dir__)).first
7
+ rescue => e
8
+ puts "Application failed to initialize:"
9
+ raise e
10
+ exit 1
11
+ end
12
+
13
+ # Migrate and seed the DB (only an empty in-memory DB)
14
+
15
+ ActiveRecord::Migration.verbose = false # ?? does not seem to work like this
16
+ ActiveRecord::Tasks::DatabaseTasks.migrate
17
+ require_relative '../db/seeds.rb'
18
+
19
+ RSpec.configure do |config|
20
+ config.include Rack::Test::Methods
21
+
22
+ config.before(:suite) do
23
+ # Praxis::Blueprint.caching_enabled = true
24
+ DatabaseCleaner.strategy = :transaction
25
+ end
26
+ config.around(:each) do |example|
27
+ DatabaseCleaner.cleaning do
28
+ example.run
29
+ end
30
+ end
31
+
32
+ # config.before(:each) do
33
+ # Praxis::Blueprint.cache = Hash.new do |hash, key|
34
+ # hash[key] = Hash.new
35
+ # end
36
+ # end
37
+
38
+ def app
39
+ APP
40
+ end
41
+ end
42
+
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ require 'spec_helper'
5
+ describe V1::Controllers::Users do
6
+ before do
7
+ header 'X-API-Version', '1'
8
+ end
9
+
10
+ let(:response) { subject; last_response }
11
+ let(:json_payload) { JSON.dump(payload) }
12
+ let(:parsed_body) { JSON.parse(response.body, symbolize_names: true) }
13
+
14
+ context 'index' do
15
+ let(:filters_q) { '' }
16
+ let(:fields_q) { 'id' }
17
+ let(:query_string) do
18
+ "filters=#{CGI.escape(filters_q)}&fields=#{CGI.escape(fields_q)}"
19
+ end
20
+ subject { get "/users?#{query_string}" }
21
+
22
+ context 'without filters' do
23
+ it { expect(response.status).to eq 200 }
24
+ it 'returns all users' do
25
+ expect(parsed_body.size).to eq(2+100)
26
+ end
27
+ end
28
+ context 'using filters' do
29
+ let(:filters_q) { 'first_name=Peter' }
30
+ it 'returns only peter' do
31
+ expect(parsed_body.size).to eq(1)
32
+ # Peter has id 11 from our seeds
33
+ expect(parsed_body.map{|u| u[:id]}).to eq([11])
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= version_module %>
4
+ module Endpoints
5
+ class <%= plural_class %>
6
+ include Praxis::EndpointDefinition
7
+
8
+ media_type MediaTypes::<%= singular_class %>
9
+ version '<%= version %>'
10
+
11
+ description 'Praxis-generated endpoint for managing <%= plural_class %>'
12
+
13
+ <%- if action_enabled?(:index) -%>
14
+ action :index do
15
+ description 'List <%= plural_class %>'
16
+ routing { get '' }
17
+ params do
18
+ attribute :fields, Praxis::Types::FieldSelector.for(MediaTypes::<%= singular_class %>),
19
+ description: 'Fields with which to render the result.'
20
+ <%- if !pagination_enabled? -%>
21
+ =begin
22
+ # You can use pagination/ordering by enabling the PaginationPlugin, and uncommenting these lines
23
+ <%- end -%>
24
+ attribute :pagination, Praxis::Types::PaginationParams.for(MediaTypes::<%= singular_class %>)
25
+ attribute :order, Praxis::Extensions::Pagination::OrderingParams.for(MediaTypes::<%= singular_class %>)
26
+ <%- if !pagination_enabled? -%>
27
+ =end
28
+ <%- end -%>
29
+ # # Filter by attributes. Add an allowed filter per line, with the allowed operators to use
30
+ # # Also, remember to add a mapping for each in `filters_mapping` method of Resources::<%= singular_class %> class
31
+ # attribute :filters, Praxis::Types::FilteringParams.for(MediaTypes::<%= singular_class %>) do
32
+ # filter 'first_name', using: ['=', '!='], fuzzy: true
33
+ # end
34
+ end
35
+ response :ok, media_type: Praxis::Collection.of(MediaTypes::<%= singular_class %>)
36
+ end
37
+ <%- end -%>
38
+
39
+ <%- if action_enabled?(:index) -%>
40
+ action :show do
41
+ description 'Retrieve details for a specific <%= singular_class %>'
42
+ routing { get '/:id' }
43
+ params do
44
+ attribute :id, required: true
45
+ attribute :fields, Praxis::Types::FieldSelector.for(MediaTypes::<%= singular_class %>),
46
+ description: 'Fields with which to render the result.'
47
+ end
48
+ response :ok
49
+ response :not_found
50
+ end
51
+ <%- end -%>
52
+
53
+ <%- if action_enabled?(:create) -%>
54
+ action :create do
55
+ description 'Create a new <%= singular_class %>'
56
+ routing { post '' }
57
+ payload reference: MediaTypes::<%= singular_class %> do
58
+ # List the attributes you accept from the one existing in the <%= singular_class %> Mediatype
59
+ # and/or fully define any other ones you allow at creation time
60
+ # attribute :name
61
+ end
62
+ response :created
63
+ response :bad_request
64
+ end
65
+ <%- end -%>
66
+
67
+ <%- if action_enabled?(:update) -%>
68
+ action :update do
69
+ description 'Update one or more attributes of an existing <%= singular_class %>'
70
+ routing { patch '/:id' }
71
+ params do
72
+ attribute :id, required: true
73
+ end
74
+ payload reference: MediaTypes::<%= singular_class %> do
75
+ # List the attributes you accept from the one existing in the <%= singular_class %> Mediatype
76
+ # and/or fully define any other ones you allow to change
77
+ # attribute :name
78
+ end
79
+ response :no_content
80
+ response :bad_request
81
+ end
82
+ <%- end -%>
83
+
84
+ <%- if action_enabled?(:update) -%>
85
+ action :delete do
86
+ description 'Deletes a <%= singular_class %>'
87
+ routing { delete '/:id' }
88
+ params do
89
+ attribute :id, required: true
90
+ end
91
+ response :no_content
92
+ response :not_found
93
+ end
94
+ <%- end -%>
95
+ end
96
+ end
97
+ end
98
+
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= version_module %>
4
+ module MediaTypes
5
+ class <%= singular_class %> < Praxis::MediaType
6
+ identifier 'application/json'
7
+
8
+ domain_model '<%= version_module %>::Resources::<%= singular_class %>'
9
+ description 'Structural definition of a <%= singular_class %>'
10
+
11
+ attributes do
12
+ attribute :id, Integer, description: '<%= singular_class %> identifier'
13
+ # <INSERT MORE ATTRIBUTES HERE>
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= version_module %>
4
+ module Controllers
5
+ class <%= plural_class %>
6
+ include Praxis::Controller
7
+
8
+ implements Endpoints::<%= plural_class %>
9
+
10
+ <%- if action_enabled?(:index) -%>
11
+ # Retrieve all <%= plural_class %> with the right necessary associations
12
+ # and render them appropriately with the requested field selection
13
+ def index
14
+ objects = build_query(model_class).all
15
+ display(objects)
16
+ end
17
+ <%- end -%>
18
+
19
+ <%- if action_enabled?(:show) -%>
20
+ # Retrieve a single <%= singular_class %> with the right necessary associations
21
+ # and render them appropriately with the requested field selection
22
+ def show(id:, **_args)
23
+ model = build_query(model_class.where(id: id)).first
24
+ return Praxis::Responses::NotFound.new if model.nil?
25
+
26
+ display(model)
27
+ end
28
+ <%- end -%>
29
+
30
+ <%- if action_enabled?(:create) -%>
31
+ # Creates a new <%= singular_class %>
32
+ def create
33
+ # A good pattern is to call the same name method on the corresponding resource,
34
+ # passing the incoming payload, or massaging it first
35
+ created_resource = Resources::<%= singular_class%>.create(request.payload)
36
+
37
+ # Respond with a created if it successfully finished
38
+ Praxis::Responses::Created.new(location: created_resource.href)
39
+ end
40
+ <%- end -%>
41
+
42
+ <%- if action_enabled?(:update) -%>
43
+ # Updates some of the information of a <%= singular_class %>
44
+ def update(id:)
45
+ # A good pattern is to call the same name method on the corresponding resource,
46
+ # passing the incoming id and payload (or massaging it first)
47
+ updated_resource = Resources::<%= singular_class %>.update(
48
+ id: id,
49
+ payload: request.payload,
50
+ )
51
+ return Praxis::Responses::NotFound.new unless updated_resource
52
+
53
+ Praxis::Responses::NoContent.new
54
+ end
55
+ <%- end -%>
56
+
57
+ <%- if action_enabled?(:delete) -%>
58
+ # Deletes an existing <%= singular_class %>
59
+ def delete(id:)
60
+ # A good pattern is to call the same name method on the corresponding resource,
61
+ # maybe passing the already loaded model
62
+ deleted_resource = Resources::<%= singular_class %>.delete(
63
+ id: id
64
+ )
65
+ return Praxis::Responses::NotFound.new unless deleted_resource
66
+
67
+ Praxis::Responses::NoContent.new
68
+ end
69
+ <%- end -%>
70
+
71
+ # Use the model class as the base query but you might want to change that
72
+ def model_class
73
+ ::<%= singular_class %> #Change it to the appropriate DB model class
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= version_module %>
4
+ module Resources
5
+ class Base < Praxis::Mapper::Resource
6
+ # Base for all <%= version_module %> resources.
7
+ # Resources withing a single version should have resource mappings separate from other versions
8
+ # and the Mapper::Resource will appropriately maintain different model_maps for each Base classes
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module <%= version_module %>
4
+ module Resources
5
+ class <%= singular_class %> < Base
6
+ model ::<%= singular_class %> # Change it if it maps to a different DB model class
7
+
8
+ # Define the name mapping from API filter params, to model attribute/associations
9
+ # when they aren't 1:1
10
+ # filters_mapping(
11
+ # 'name': 'name',
12
+ # 'label': 'association.label_name'
13
+ # )
14
+
15
+ # Add dependencies for resource attributes to other attributes and/or model associations
16
+ # property :href, dependencies: %i[id]
17
+
18
+ <%- if action_enabled?(:create) -%>
19
+ def self.create(payload)
20
+ # Assuming the API field names directly map the the model attributes. Massage if appropriate.
21
+ self.new(model.create(*payload.to_h))
22
+ end
23
+ <%- end -%>
24
+
25
+ <%- if action_enabled?(:update) -%>
26
+ def self.update(id:, payload:)
27
+ record = model.find_by(id: id)
28
+ return nil unless record
29
+ # Assuming the API field names directly map the the model attributes. Massage if appropriate.
30
+ record.update(*payload.to_h)
31
+ self.new(record)
32
+ end
33
+ <%- end -%>
34
+
35
+ <%- if action_enabled?(:delete) -%>
36
+ def self.delete(id:)
37
+ record = model.find_by(id: id)
38
+ return nil unless record
39
+ record.destroy
40
+ self.new(record)
41
+ end
42
+ <%- end -%>
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= model_class %> < ActiveRecord::Base
4
+ include Praxis::Mapper::ActiveModelCompat
5
+
6
+ end