praxis 2.0.pre.10 → 2.0.pre.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -3
  4. data/CHANGELOG.md +26 -0
  5. data/bin/praxis +65 -2
  6. data/lib/praxis/api_definition.rb +8 -4
  7. data/lib/praxis/bootloader_stages/environment.rb +1 -0
  8. data/lib/praxis/collection.rb +11 -0
  9. data/lib/praxis/docs/open_api/response_object.rb +21 -6
  10. data/lib/praxis/docs/open_api_generator.rb +1 -1
  11. data/lib/praxis/extensions/attribute_filtering.rb +14 -1
  12. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +206 -66
  13. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
  14. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +45 -41
  15. data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +193 -0
  16. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
  17. data/lib/praxis/extensions/pagination.rb +5 -32
  18. data/lib/praxis/mapper/active_model_compat.rb +4 -0
  19. data/lib/praxis/mapper/resource.rb +18 -2
  20. data/lib/praxis/mapper/selector_generator.rb +1 -0
  21. data/lib/praxis/mapper/sequel_compat.rb +7 -0
  22. data/lib/praxis/media_type_identifier.rb +11 -1
  23. data/lib/praxis/plugins/mapper_plugin.rb +22 -13
  24. data/lib/praxis/plugins/pagination_plugin.rb +34 -4
  25. data/lib/praxis/response_definition.rb +46 -66
  26. data/lib/praxis/responses/http.rb +3 -1
  27. data/lib/praxis/tasks/api_docs.rb +4 -1
  28. data/lib/praxis/tasks/routes.rb +6 -6
  29. data/lib/praxis/version.rb +1 -1
  30. data/spec/praxis/action_definition_spec.rb +3 -1
  31. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +267 -167
  32. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
  33. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +100 -17
  34. data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +148 -0
  35. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
  36. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
  37. data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
  38. data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
  39. data/spec/praxis/media_type_identifier_spec.rb +15 -1
  40. data/spec/praxis/response_definition_spec.rb +37 -129
  41. data/tasks/thor/example.rb +12 -6
  42. data/tasks/thor/model.rb +40 -0
  43. data/tasks/thor/scaffold.rb +117 -0
  44. data/tasks/thor/templates/generator/empty_app/config/environment.rb +1 -0
  45. data/tasks/thor/templates/generator/example_app/Rakefile +9 -2
  46. data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
  47. data/tasks/thor/templates/generator/example_app/app/v1/concerns/href.rb +33 -0
  48. data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +2 -2
  49. data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +15 -0
  50. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +7 -28
  51. data/tasks/thor/templates/generator/example_app/config.ru +1 -2
  52. data/tasks/thor/templates/generator/example_app/config/environment.rb +3 -2
  53. data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +3 -2
  54. data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
  55. data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +4 -4
  56. data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +1 -6
  57. data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +4 -2
  58. data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +2 -2
  59. data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +2 -2
  60. data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
  61. data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
  62. data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
  63. data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
  64. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
  65. data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
  66. data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
  67. metadata +21 -6
@@ -5,8 +5,9 @@ class DatabaseHelper
5
5
  # This does the job for an example seeder
6
6
  def self.seed!
7
7
  user_data = [
8
- {id: 11, first_name: 'Peter', last_name: 'Praxis', uuid: 'deadbeef'},
9
- {id: 12, first_name: 'Alice', last_name: 'Trellis', uuid: 'beefdead'}
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'},
10
11
  ]
11
12
  (100..199).each do |i|
12
13
  user_data.push id: i, first_name: "User-#{i}", last_name: "Last-#{i}", uuid: SecureRandom.hex(16).to_s
@@ -14,5 +15,6 @@ class DatabaseHelper
14
15
  user_data.each_with_index do |data, i|
15
16
  ::User.create(**data)
16
17
  end
18
+ puts "Database seeded."
17
19
  end
18
20
  end
@@ -11,10 +11,10 @@ rescue => e
11
11
  end
12
12
 
13
13
  # Migrate and seed the DB (only an empty in-memory DB)
14
- require_relative 'helpers/database_helper'
14
+
15
15
  ActiveRecord::Migration.verbose = false # ?? does not seem to work like this
16
16
  ActiveRecord::Tasks::DatabaseTasks.migrate
17
- DatabaseHelper.seed!
17
+ require_relative '../db/seeds.rb'
18
18
 
19
19
  RSpec.configure do |config|
20
20
  config.include Rack::Test::Methods
@@ -13,7 +13,7 @@ describe V1::Controllers::Users do
13
13
 
14
14
  context 'index' do
15
15
  let(:filters_q) { '' }
16
- let(:fields_q) { 'uid,uuid' }
16
+ let(:fields_q) { 'id' }
17
17
  let(:query_string) do
18
18
  "filters=#{CGI.escape(filters_q)}&fields=#{CGI.escape(fields_q)}"
19
19
  end
@@ -30,7 +30,7 @@ describe V1::Controllers::Users do
30
30
  it 'returns only peter' do
31
31
  expect(parsed_body.size).to eq(1)
32
32
  # Peter has id 11 from our seeds
33
- expect(parsed_body.map{|u| u[:uid]}).to eq(['11'])
33
+ expect(parsed_body.map{|u| u[:id]}).to eq([11])
34
34
  end
35
35
  end
36
36
  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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= model_class %> < Sequel::Model
4
+ include Praxis::Mapper::SequelCompat
5
+
6
+ end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: praxis
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.pre.10
4
+ version: 2.0.pre.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
8
8
  - Dane Jensen
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-12-10 00:00:00.000000000 Z
12
+ date: 2021-04-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -367,7 +367,7 @@ dependencies:
367
367
  - - ">"
368
368
  - !ruby/object:Gem::Version
369
369
  version: '4'
370
- description:
370
+ description:
371
371
  email:
372
372
  - blanquer@gmail.com
373
373
  - dane.jensen@gmail.com
@@ -446,6 +446,7 @@ files:
446
446
  - lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb
447
447
  - lib/praxis/extensions/attribute_filtering/filter_tree_node.rb
448
448
  - lib/praxis/extensions/attribute_filtering/filtering_params.rb
449
+ - lib/praxis/extensions/attribute_filtering/filters_parser.rb
449
450
  - lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb
450
451
  - lib/praxis/extensions/field_expansion.rb
451
452
  - lib/praxis/extensions/field_selection.rb
@@ -540,6 +541,7 @@ files:
540
541
  - spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb
541
542
  - spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb
542
543
  - spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb
544
+ - spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb
543
545
  - spec/praxis/extensions/field_expansion_spec.rb
544
546
  - spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb
545
547
  - spec/praxis/extensions/field_selection/field_selector_spec.rb
@@ -614,6 +616,8 @@ files:
614
616
  - tasks/loader.thor
615
617
  - tasks/thor/app.rb
616
618
  - tasks/thor/example.rb
619
+ - tasks/thor/model.rb
620
+ - tasks/thor/scaffold.rb
617
621
  - tasks/thor/templates/generator/empty_app/.gitignore
618
622
  - tasks/thor/templates/generator/empty_app/Gemfile
619
623
  - tasks/thor/templates/generator/empty_app/README.md
@@ -642,22 +646,33 @@ files:
642
646
  - tasks/thor/templates/generator/example_app/Gemfile
643
647
  - tasks/thor/templates/generator/example_app/Rakefile
644
648
  - tasks/thor/templates/generator/example_app/app/models/user.rb
649
+ - tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb
650
+ - tasks/thor/templates/generator/example_app/app/v1/concerns/href.rb
645
651
  - tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb
652
+ - tasks/thor/templates/generator/example_app/app/v1/resources/base.rb
646
653
  - tasks/thor/templates/generator/example_app/app/v1/resources/user.rb
647
654
  - tasks/thor/templates/generator/example_app/config.ru
648
655
  - tasks/thor/templates/generator/example_app/config/environment.rb
649
656
  - tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb
657
+ - tasks/thor/templates/generator/example_app/db/seeds.rb
650
658
  - tasks/thor/templates/generator/example_app/design/api.rb
651
659
  - tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb
652
660
  - tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb
653
661
  - tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb
654
662
  - tasks/thor/templates/generator/example_app/spec/spec_helper.rb
655
663
  - tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb
664
+ - tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb
665
+ - tasks/thor/templates/generator/scaffold/design/media_types/item.rb
666
+ - tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb
667
+ - tasks/thor/templates/generator/scaffold/implementation/resources/base.rb
668
+ - tasks/thor/templates/generator/scaffold/implementation/resources/item.rb
669
+ - tasks/thor/templates/generator/scaffold/models/active_record.rb
670
+ - tasks/thor/templates/generator/scaffold/models/sequel.rb
656
671
  homepage: https://github.com/praxis/praxis
657
672
  licenses:
658
673
  - MIT
659
674
  metadata: {}
660
- post_install_message:
675
+ post_install_message:
661
676
  rdoc_options: []
662
677
  require_paths:
663
678
  - lib
@@ -673,7 +688,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
673
688
  version: 1.3.1
674
689
  requirements: []
675
690
  rubygems_version: 3.1.2
676
- signing_key:
691
+ signing_key:
677
692
  specification_version: 4
678
693
  summary: Building APIs the way you want it.
679
694
  test_files: []