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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -3
- data/CHANGELOG.md +33 -0
- data/TODO.md +1 -4
- data/bin/praxis +67 -12
- data/lib/praxis.rb +10 -3
- data/lib/praxis/action_definition.rb +15 -13
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
- data/lib/praxis/api_general_info.rb +1 -1
- data/lib/praxis/application.rb +6 -2
- data/lib/praxis/blueprint.rb +357 -0
- data/lib/praxis/bootloader.rb +9 -3
- data/lib/praxis/bootloader_stages/environment.rb +16 -13
- data/lib/praxis/collection.rb +1 -11
- data/lib/praxis/config_hash.rb +44 -0
- data/lib/praxis/docs/{openapi → open_api}/info_object.rb +18 -10
- data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
- data/lib/praxis/docs/open_api_generator.rb +91 -6
- data/lib/praxis/endpoint_definition.rb +273 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +182 -58
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +47 -56
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +153 -0
- data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
- data/lib/praxis/extensions/field_expansion.rb +3 -36
- data/lib/praxis/extensions/pagination.rb +5 -32
- data/lib/praxis/extensions/pagination/ordering_params.rb +1 -1
- data/lib/praxis/extensions/pagination/pagination_params.rb +6 -4
- data/lib/praxis/field_expander.rb +90 -0
- data/lib/praxis/finalizable.rb +34 -0
- data/lib/praxis/mapper/active_model_compat.rb +4 -0
- data/lib/praxis/mapper/resource.rb +18 -2
- data/lib/praxis/mapper/selector_generator.rb +2 -1
- data/lib/praxis/mapper/sequel_compat.rb +7 -0
- data/lib/praxis/media_type.rb +3 -68
- data/lib/praxis/plugin_concern.rb +1 -1
- data/lib/praxis/plugins/mapper_plugin.rb +24 -15
- data/lib/praxis/plugins/pagination_plugin.rb +34 -4
- data/lib/praxis/renderer.rb +88 -0
- data/lib/praxis/request.rb +1 -1
- data/lib/praxis/resource_definition.rb +2 -311
- data/lib/praxis/response_definition.rb +2 -10
- data/lib/praxis/response_template.rb +3 -3
- data/lib/praxis/router.rb +2 -2
- data/lib/praxis/routing_config.rb +1 -1
- data/lib/praxis/tasks/api_docs.rb +17 -64
- data/lib/praxis/tasks/routes.rb +1 -1
- data/lib/praxis/types/media_type_common.rb +1 -11
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +0 -1
- data/spec/functional_spec.rb +5 -9
- data/spec/praxis/action_definition_spec.rb +12 -20
- data/spec/praxis/blueprint_spec.rb +373 -0
- data/spec/praxis/bootloader_spec.rb +10 -2
- data/spec/praxis/collection_spec.rb +0 -13
- data/spec/praxis/config_hash_spec.rb +64 -0
- data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +37 -64
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +249 -168
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +190 -8
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +140 -0
- data/spec/praxis/extensions/field_expansion_spec.rb +5 -24
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
- data/spec/praxis/field_expander_spec.rb +149 -0
- data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
- data/spec/praxis/media_type_identifier_spec.rb +5 -4
- data/spec/praxis/media_type_spec.rb +4 -93
- data/spec/praxis/renderer_spec.rb +188 -0
- data/spec/praxis/response_definition_spec.rb +0 -31
- data/spec/praxis/response_spec.rb +1 -1
- data/spec/praxis/router_spec.rb +8 -8
- data/spec/praxis/routing_config_spec.rb +3 -3
- data/spec/spec_app/app/controllers/instances.rb +13 -7
- data/spec/spec_app/design/media_types/instance.rb +1 -19
- data/spec/spec_app/design/media_types/volume.rb +1 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
- data/spec/spec_app/design/resources/instances.rb +5 -8
- data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
- data/spec/spec_app/design/resources/volumes.rb +1 -1
- data/spec/support/spec_authorization_plugin.rb +1 -1
- data/spec/support/spec_blueprints.rb +72 -0
- data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
- data/spec/support/spec_media_types.rb +6 -26
- data/tasks/thor/app.rb +8 -34
- data/tasks/thor/example.rb +51 -285
- data/tasks/thor/model.rb +40 -0
- data/tasks/thor/scaffold.rb +117 -0
- data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
- data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
- data/tasks/thor/templates/generator/empty_app/README.md +1 -1
- data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
- data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/config/environment.rb +26 -17
- data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
- data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
- data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
- data/tasks/thor/templates/generator/example_app/Rakefile +61 -0
- data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
- data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +25 -0
- data/tasks/thor/templates/generator/example_app/config.ru +30 -0
- data/tasks/thor/templates/generator/example_app/config/environment.rb +41 -0
- data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +12 -0
- data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
- data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
- data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
- data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +21 -0
- data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +20 -0
- data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
- data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
- data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
- data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
- data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
- data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
- data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
- metadata +64 -136
- data/lib/api_browser/.bowerrc +0 -3
- data/lib/api_browser/.editorconfig +0 -21
- data/lib/api_browser/Gruntfile.js +0 -581
- data/lib/api_browser/app/index.html +0 -59
- data/lib/api_browser/app/js/app.js +0 -48
- data/lib/api_browser/app/js/controllers/action.js +0 -47
- data/lib/api_browser/app/js/controllers/controller.js +0 -10
- data/lib/api_browser/app/js/controllers/menu.js +0 -93
- data/lib/api_browser/app/js/controllers/trait.js +0 -10
- data/lib/api_browser/app/js/controllers/type.js +0 -24
- data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
- data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
- data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
- data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
- data/lib/api_browser/app/js/directives/highlight.js +0 -14
- data/lib/api_browser/app/js/directives/menu_item.js +0 -59
- data/lib/api_browser/app/js/directives/no_container.js +0 -8
- data/lib/api_browser/app/js/directives/readable_list.js +0 -87
- data/lib/api_browser/app/js/directives/request_examples.js +0 -31
- data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
- data/lib/api_browser/app/js/directives/url.js +0 -15
- data/lib/api_browser/app/js/factories/Configuration.js +0 -12
- data/lib/api_browser/app/js/factories/Documentation.js +0 -61
- data/lib/api_browser/app/js/factories/Example.js +0 -51
- data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
- data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
- data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
- data/lib/api_browser/app/js/factories/template_for.js +0 -128
- data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
- data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
- data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
- data/lib/api_browser/app/js/filters/header_info.js +0 -9
- data/lib/api_browser/app/js/filters/is_empty.js +0 -8
- data/lib/api_browser/app/js/filters/markdown.js +0 -6
- data/lib/api_browser/app/js/filters/resource_name.js +0 -5
- data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
- data/lib/api_browser/app/sass/modules/_body.scss +0 -40
- data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
- data/lib/api_browser/app/sass/modules/_header.scss +0 -10
- data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
- data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
- data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
- data/lib/api_browser/app/sass/modules/_table.scss +0 -13
- data/lib/api_browser/app/sass/praxis.scss +0 -70
- data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
- data/lib/api_browser/app/views/action.html +0 -97
- data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
- data/lib/api_browser/app/views/controller.html +0 -55
- data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
- data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
- data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
- data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
- data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
- data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
- data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
- data/lib/api_browser/app/views/directives/menu_item.html +0 -8
- data/lib/api_browser/app/views/directives/url.html +0 -3
- data/lib/api_browser/app/views/examples/general.html +0 -26
- data/lib/api_browser/app/views/home.html +0 -5
- data/lib/api_browser/app/views/layout.html +0 -8
- data/lib/api_browser/app/views/menu.html +0 -42
- data/lib/api_browser/app/views/navbar.html +0 -9
- data/lib/api_browser/app/views/trait.html +0 -13
- data/lib/api_browser/app/views/type.html +0 -6
- data/lib/api_browser/app/views/type/details.html +0 -33
- data/lib/api_browser/app/views/types/embedded/array.html +0 -2
- data/lib/api_browser/app/views/types/embedded/default.html +0 -12
- data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
- data/lib/api_browser/app/views/types/embedded/links.html +0 -11
- data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
- data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
- data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
- data/lib/api_browser/app/views/types/label/link.html +0 -1
- data/lib/api_browser/app/views/types/label/primitive.html +0 -1
- data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
- data/lib/api_browser/app/views/types/label/type.html +0 -1
- data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
- data/lib/api_browser/app/views/types/main/array.html +0 -22
- data/lib/api_browser/app/views/types/main/default.html +0 -23
- data/lib/api_browser/app/views/types/main/hash.html +0 -23
- data/lib/api_browser/app/views/types/standalone/array.html +0 -3
- data/lib/api_browser/app/views/types/standalone/default.html +0 -18
- data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
- data/lib/api_browser/bower_template.json +0 -41
- data/lib/api_browser/package-lock.json +0 -7110
- data/lib/api_browser/package.json +0 -43
- data/lib/praxis/docs/generator.rb +0 -243
- data/lib/praxis/docs/link_builder.rb +0 -30
- data/lib/praxis/links.rb +0 -135
- data/lib/praxis/types/multipart.rb +0 -109
- data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
- data/spec/api_browser/factories/configuration_spec.js +0 -32
- data/spec/api_browser/factories/documentation_spec.js +0 -100
- data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
- data/spec/api_browser/factories/template_for_spec.js +0 -67
- data/spec/api_browser/filters/attribute_name_spec.js +0 -23
- data/spec/praxis/types/multipart_spec.rb +0 -112
- data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
- data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
- data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
- data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
- 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
|