praxis 2.0.pre.31 → 2.0.pre.33

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +4 -1
  4. data/Appraisals +11 -0
  5. data/CHANGELOG.md +144 -104
  6. data/Gemfile +6 -6
  7. data/bin/praxis +24 -1
  8. data/gemfiles/active_6.gemfile +16 -0
  9. data/gemfiles/active_6.gemfile.lock +199 -0
  10. data/gemfiles/active_7.gemfile +16 -0
  11. data/gemfiles/active_7.gemfile.lock +197 -0
  12. data/lib/praxis/action_definition/headers_dsl_compiler.rb +1 -1
  13. data/lib/praxis/application.rb +1 -1
  14. data/lib/praxis/blueprint.rb +25 -18
  15. data/lib/praxis/blueprint_attribute_group.rb +0 -2
  16. data/lib/praxis/controller.rb +4 -0
  17. data/lib/praxis/docs/open_api/operation_object.rb +9 -0
  18. data/lib/praxis/docs/open_api/paths_object.rb +2 -2
  19. data/lib/praxis/docs/open_api_generator.rb +51 -21
  20. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +4 -4
  21. data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +6 -12
  22. data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +2 -0
  23. data/lib/praxis/extensions/pagination/pagination_params.rb +2 -2
  24. data/lib/praxis/field_expander.rb +1 -1
  25. data/lib/praxis/mapper/active_model_compat.rb +4 -6
  26. data/lib/praxis/mapper/selector_generator.rb +1 -1
  27. data/lib/praxis/media_type_identifier.rb +4 -4
  28. data/lib/praxis/request.rb +1 -1
  29. data/lib/praxis/tasks/console.rb +3 -0
  30. data/lib/praxis/types/multipart_array.rb +3 -3
  31. data/lib/praxis/version.rb +1 -1
  32. data/praxis.gemspec +2 -4
  33. data/spec/praxis/application_spec.rb +11 -0
  34. data/spec/praxis/blueprint_spec.rb +307 -17
  35. data/spec/praxis/controller_spec.rb +9 -0
  36. data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +28 -0
  37. data/spec/praxis/request_spec.rb +10 -0
  38. data/spec/support/spec_blueprints.rb +6 -4
  39. data/tasks/thor/model.rb +3 -1
  40. data/tasks/thor/scaffold.rb +35 -3
  41. data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +1 -0
  42. data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +1 -1
  43. data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +11 -14
  44. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +3 -7
  45. metadata +23 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b0d313f269a5d2eee55bec87524dc37d0b424ed747969fa35f3f8454e658028
4
- data.tar.gz: 894589492bcd9d0cd1c8af237215892e163c9229f6f81a6aa90a709b43623a25
3
+ metadata.gz: 9bdbabd51b8b6bb4a776ab4c728d36b3cf86f3b7a3423250e6658fa01653ba6a
4
+ data.tar.gz: e9edc1eacc8ebf67d8f8b5fdda19cb010b587f3e2d0319cbcdeec62908752d8b
5
5
  SHA512:
6
- metadata.gz: 1744d64f7e8521185d687cc124c8b553b297f79734c74b0bffb83bab7a5f8bdbacf1e71899ffce8044fddbf1d92902270fae4892425808aaedc6eff7a2490f61
7
- data.tar.gz: c5602b15ddf593f4b387f39c398fd411158e2b936e5763a21cf01edb86c3ea4c87f3a09f95c5fa2bff29317075ebae6bbec52eba5190b9594359e335e14ef23f
6
+ metadata.gz: 174e74b7e4e55378edde0f623b4f2c49aa15409bfc64ae9f36aa8a0167794ed1c3b747331da5bdc66e672bcb58228cbaad89971689449d082d5080c009b7ad6c
7
+ data.tar.gz: c7574f73697e2067e6f338d1c19bdc6447ca24620ed49a1ff94882392c8df987cbc598624323ce3228330c27ce0dda7b4f0228b68a4ad18dbf6c37c995869e84
data/.gitignore CHANGED
@@ -3,6 +3,7 @@ coverage
3
3
  Gemfile.lock
4
4
  _site/
5
5
  .sass-cache/
6
+ .vscode
6
7
 
7
8
  .data/
8
9
  .tmp/
@@ -15,4 +16,4 @@ lib/api_browser/app/bower_components/
15
16
  lib/api_browser/.tmp/
16
17
  lib/api_browser/bower.json
17
18
 
18
- development.sqlite3
19
+ development.sqlite3
data/.travis.yml CHANGED
@@ -9,4 +9,7 @@ script:
9
9
  branches:
10
10
  only:
11
11
  - master
12
- - /.*/
12
+ - /.*/
13
+ gemfile:
14
+ - gemfiles/active_6.gemfile
15
+ - gemfiles/active_7.gemfile
data/Appraisals ADDED
@@ -0,0 +1,11 @@
1
+ appraise "active-6" do
2
+ # Just for the query selector/filtering/ordering extensions etc...
3
+ gem 'activerecord', '> 4', '< 7'
4
+ gem 'sequel', '~> 5'
5
+ end
6
+
7
+ appraise "active-7" do
8
+ # Just for the query selector/filtering/ordering extensions etc...
9
+ gem 'activerecord', '>=7'
10
+ gem 'sequel', '~> 5'
11
+ end
data/CHANGELOG.md CHANGED
@@ -1,144 +1,183 @@
1
1
  # Praxis Changelog
2
2
 
3
3
  ## next
4
+ - Better support for ordefing in newer versions of MySQL:
5
+ * Some versions will complain on an invalid query if you use an ORDER BY component that does not have the corresponding SELECT field
6
+ - Use the newest Attributor gem, which revamps and hardnens the struct/collection definion DSL, and provides much better error messages and safety. It also
7
+ bring the ability to define collections with `<T>[]` which is equivalent to the current `Attributor::Collection.of(<T>). For example, you can now do things
8
+ like `String[]`, `MyMediaType[]`, etc..
9
+ - Tightened a few type comparisons throughout the framework, and built full specs for struct/collection definitions in Blueprints.
10
+ - Built config for the Appraisals gem, so we can continuously test some of our extensions against different versions of ActiveRecord as it evolves (6x and7x for now)
11
+
12
+ ## 2.0.pre.32
13
+ - Spruced up the scaffolding generation, to be more configurable using a `.praxis_scaffold` file at the root, where one can specify things like
14
+ the base module for all generated classes (`base`), the path of the models directory (`models_dir`) and the version to use (`version`). These, except the models directory can also be passed and overriden by command line arguments (and they would be saved into the config file to be usable in future invocations)
15
+ - More efficient validation of Blueprint structures
16
+ - Fix pseudo bug where the field expander capped subfields as soon as it encountered a type without explicit attributes (i.e., Hash). Instead, it allows any of the subfields to percolate through.
17
+ - OpenApi generation improvements:
18
+ - Add global parameters for versioning (ApiVersionHeader and ApiVersionParam) appropriately if the API is versioned by them. Have actions point to these definitions by $ref
19
+ - Expect the 'server' definition in the APIDefinition to contain url/description and 'variables' sections which might define variables in the server url
4
20
 
5
21
  ## 2.0.pre.31
6
- * Switch the locally generated index.html file to browse the generated OpenAPI docs to use `elements` instead of `reDoc`
7
- * Spruce up the initial Gemfile for the generated example app
8
- * Fix Praxis::Mapper ordering code, to not prefix top level columns with the table name, as this completely confuses ActiveRecord which switches to full-on eager loading all of the associations in the top query. i.e., passing an invalid table/column name in a `references` method will trigger that (seemingly a current bug)
22
+
23
+ - Switch the locally generated index.html file to browse the generated OpenAPI docs to use `elements` instead of `reDoc`
24
+ - Spruce up the initial Gemfile for the generated example app
25
+ - Fix Praxis::Mapper ordering code, to not prefix top level columns with the table name, as this completely confuses ActiveRecord which switches to full-on eager loading all of the associations in the top query. i.e., passing an invalid table/column name in a `references` method will trigger that (seemingly a current bug)
9
26
 
10
27
  ## 2.0.pre.30
11
- * A few cleanup and robustness additions:
12
- * OpenAPI: Disable overriding a description when the schema is a ref (there are known issues with UI browsers)
13
- * Internal: use `_pk` in batch processor invocation instead of `id` (resources will now have a `_pk` method which defaults to `id`)
14
- * Bumped gemspec Ruby dependency to >=2.7 (but note, that this is just a little relaxed for older codebase, we're fully building for 3.x)
15
- * Backwards incompatible changes:
16
- * Enforces property names are symbols (before strings were allowed)
17
- * Resource properties, using the `as:` option, are now enforced to be real association names (will not accept other resource names and unroll their dependencies)
18
- * Deprecated the `:through` option for a property. You can just use `as:` with a long, dot-separated association path directly.
19
- * Enhanced ordering semantics for pagination to allow for sorting of deep associated fields:
20
- * Right now, you can sort by fields such as `books.author.name` as one of the sorting components (with `+` or `-` still available)
21
- * Introduced better attribute grouping concepts, that help in defining subgroups of attributes of the same object, and allow lazy loading of only partial subsets so that one can have expensive computations on some of them, but they will never be invoked unless necessary. See MediaType.`group` and Resoruce.`property_group` explanations below.
22
- * Introduced a 'group' stanza in MediaTypes, to specify a structure of attributes that exist in the main object, but that we want to neatly expose as a subset (instead of having them unrolled at the top):
23
- * You can now use things like `group subinfo do ... end` blocks, defining which attributes to group
24
- * Internal: Underneath, the system will create a BlueprintAttributeGroup (instead of a Struct) as a way to ensure that only the individual attributes that need to be rendered, are accessed (and not load the whole struct at once). While the behavior, to the outside, is gonna be identical to a Struct (i.e., exposes attributes as methods), this distinct object implementation is very important as it allows you to have attributes in the subgroup that are expensive to compute, and can be rest assured that they will not be accessed/computed unless they are required for rendering.
25
- * Introduced the `property_group` stanza in resources, to indicate that a property contains a substructure of attributes, each of which must be able to be loaded only when necessary. This commonly goes hand in hand with a `group` stanza in the resource's MediaType:
26
- * Usage of property group requires the name of the substructure (a symbol), and the associated mediatype that contains the definition of the `group` struct, under the same name of the property.
27
- * Internally, this stanza, will define a normal property, and include as dependencies all of the sub attributes read from the MediaType's property, but appending the name (and `_`) to them to avoid collisions.
28
- * Also, it will define a method with the property name which will return a Forwarding object, which will delegate each of the attribute methods back to the original self objects. This allows the object to avoid being 'loaded' as a whole as it happens with Struct, therefore only materializing/calling the attribute that we actually need to use, selectively.
29
- * For example, if we have the `Book` MediaType which has a group atrribute called `subinfo` with a few attributes (like `name` and `pages`), we can use `property_group :subinfo, Book` on its domain object, so that the system will:
30
- * define a `subinfo` property which will depend on `subinfo_name` and `subinfo_pages`
31
- * define a `subinfo` method that will return a Forwarding object, that will forward `name` and `pages` methods to `subinfo_name` and `subinfo_pages` methods of the self resource.
32
- * with that, we just need to define our `subinfo_name` and `subinfo_page` methods in the resource (and also define property dependencies for them if we need to)
28
+
29
+ - A few cleanup and robustness additions:
30
+ - OpenAPI: Disable overriding a description when the schema is a ref (there are known issues with UI browsers)
31
+ - Internal: use `_pk` in batch processor invocation instead of `id` (resources will now have a `_pk` method which defaults to `id`)
32
+ - Bumped gemspec Ruby dependency to >=2.7 (but note, that this is just a little relaxed for older codebase, we're fully building for 3.x)
33
+ - Backwards incompatible changes:
34
+ - Enforces property names are symbols (before strings were allowed)
35
+ - Resource properties, using the `as:` option, are now enforced to be real association names (will not accept other resource names and unroll their dependencies)
36
+ - Deprecated the `:through` option for a property. You can just use `as:` with a long, dot-separated association path directly.
37
+ - Enhanced ordering semantics for pagination to allow for sorting of deep associated fields:
38
+ - Right now, you can sort by fields such as `books.author.name` as one of the sorting components (with `+` or `-` still available)
39
+ - Introduced better attribute grouping concepts, that help in defining subgroups of attributes of the same object, and allow lazy loading of only partial subsets so that one can have expensive computations on some of them, but they will never be invoked unless necessary. See MediaType.`group` and Resoruce.`property_group` explanations below.
40
+ - Introduced a 'group' stanza in MediaTypes, to specify a structure of attributes that exist in the main object, but that we want to neatly expose as a subset (instead of having them unrolled at the top):
41
+ - You can now use things like `group subinfo do ... end` blocks, defining which attributes to group
42
+ - Internal: Underneath, the system will create a BlueprintAttributeGroup (instead of a Struct) as a way to ensure that only the individual attributes that need to be rendered, are accessed (and not load the whole struct at once). While the behavior, to the outside, is gonna be identical to a Struct (i.e., exposes attributes as methods), this distinct object implementation is very important as it allows you to have attributes in the subgroup that are expensive to compute, and can be rest assured that they will not be accessed/computed unless they are required for rendering.
43
+ - Introduced the `property_group` stanza in resources, to indicate that a property contains a substructure of attributes, each of which must be able to be loaded only when necessary. This commonly goes hand in hand with a `group` stanza in the resource's MediaType:
44
+ - Usage of property group requires the name of the substructure (a symbol), and the associated mediatype that contains the definition of the `group` struct, under the same name of the property.
45
+ - Internally, this stanza, will define a normal property, and include as dependencies all of the sub attributes read from the MediaType's property, but appending the name (and `_`) to them to avoid collisions.
46
+ - Also, it will define a method with the property name which will return a Forwarding object, which will delegate each of the attribute methods back to the original self objects. This allows the object to avoid being 'loaded' as a whole as it happens with Struct, therefore only materializing/calling the attribute that we actually need to use, selectively.
47
+ - For example, if we have the `Book` MediaType which has a group atrribute called `subinfo` with a few attributes (like `name` and `pages`), we can use `property_group :subinfo, Book` on its domain object, so that the system will:
48
+ - define a `subinfo` property which will depend on `subinfo_name` and `subinfo_pages`
49
+ - define a `subinfo` method that will return a Forwarding object, that will forward `name` and `pages` methods to `subinfo_name` and `subinfo_pages` methods of the self resource.
50
+ - with that, we just need to define our `subinfo_name` and `subinfo_page` methods in the resource (and also define property dependencies for them if we need to)
33
51
 
34
52
  ## 2.0.pre.29
35
- * Assorted set of fixes to generate cleaner and more compliant OpenApi documents.
36
- * Mostly in the area of multipart generation, and requirements and nullability for OpenApi 3.0
53
+
54
+ - Assorted set of fixes to generate cleaner and more compliant OpenApi documents.
55
+ - Mostly in the area of multipart generation, and requirements and nullability for OpenApi 3.0
56
+
37
57
  ## 2.0.pre.28
38
- * Enhance the mapper's Resource property to allow for a couple more powerful options using the `as:` keyword:
39
- * `as: :self` will provide a way to map any further incoming fields on top of the already existing object. This is useful when we want to expose some properties for a resource, grouped within a sub structure, but that in reality they exist directly in the resource's underlying model (i.e., to organize the information of the model in a more structured/groupable way).
40
- * `as: 'association1.association2'` allows us to traverse more than 1 association, and continue applying the incoming fields under that. This is commonly used when we want to expose a relationship on a resource, which is really coming from more than a single association level depth.
58
+
59
+ - Enhance the mapper's Resource property to allow for a couple more powerful options using the `as:` keyword:
60
+ - `as: :self` will provide a way to map any further incoming fields on top of the already existing object. This is useful when we want to expose some properties for a resource, grouped within a sub structure, but that in reality they exist directly in the resource's underlying model (i.e., to organize the information of the model in a more structured/groupable way).
61
+ - `as: 'association1.association2'` allows us to traverse more than 1 association, and continue applying the incoming fields under that. This is commonly used when we want to expose a relationship on a resource, which is really coming from more than a single association level depth.
62
+
41
63
  ## 2.0.pre.27
42
- * Introduce a new `as:` option for resource's `property`, to indicate that the underlying association method it is connected to, has a different name.
43
- * This also will create a delegation function for the property name, that instead of calling the underlying association on the record, and wrapping the result with a resource instance, it will simply call the aliased method name (which is likely gonna hit the autogenerated code for that properyty, unless we have overriden it)
44
- * With this change, the selector generator (i.e., the thing that looks at the incoming `fields` parameters and calculates which select and includes are necessary to query all the data we need), will be able to understand this aliasing cases, and properly pass along, and continue expanding any nested fields that are under the property name (before this, and further inner fields would be not included as soon as we hit a property that didn't have that direct association underneath).
64
+
65
+ - Introduce a new `as:` option for resource's `property`, to indicate that the underlying association method it is connected to, has a different name.
66
+ - This also will create a delegation function for the property name, that instead of calling the underlying association on the record, and wrapping the result with a resource instance, it will simply call the aliased method name (which is likely gonna hit the autogenerated code for that properyty, unless we have overriden it)
67
+ - With this change, the selector generator (i.e., the thing that looks at the incoming `fields` parameters and calculates which select and includes are necessary to query all the data we need), will be able to understand this aliasing cases, and properly pass along, and continue expanding any nested fields that are under the property name (before this, and further inner fields would be not included as soon as we hit a property that didn't have that direct association underneath).
45
68
 
46
69
  ## 2.0.pre.26
47
- * Make POST action forwarding more robust against technically malformed GET requests with no body but passing `Content-Type`. This could cause issues when using the `enable_large_params_proxy_action` DSL.
70
+
71
+ - Make POST action forwarding more robust against technically malformed GET requests with no body but passing `Content-Type`. This could cause issues when using the `enable_large_params_proxy_action` DSL.
48
72
 
49
73
  ## 2.0.pre.25
50
- * Improve surfacing of requirement attributes in Structs for OpenApi generated documentation
51
- * Introduction of a new dsl `enable_large_params_proxy_action` for GET verb action definitions. When used, two things will happen:
52
- * A new POST verb equivalent action will be defined:
53
- * It will have a `payload` matching the shape of the original GET's params (with the exception of any param that was originally in the URL)
54
- * By default, the route for this new POST request is gonna have the same URL as the original GET action, but appending `/actions/<action_name>` to it. This can be customized by passing the path with the `at:` parameter of the DSL. I.e., `enable_large_params_proxy_action at: /actions/myspecialname` will change the generated path (can use the `//...` syntax to not include the prefix defined for the endpoint). NOTE: this route needs to be compatible with any params that might be defined for the URL (i.e., `:id` and such).
55
- * This action will be fully visible and fully documented in the API generated docs. However, it will not need to have a corresponding controller implementation since it will special-forward it to the original GET action switching the parameters for the payload.
56
- * Specifically, upon receiving a request to the POST equivalent action, Praxis will detect it is a special action and will:
57
- * use directly the original action (i.e., will do the before/after filters and call the controller's method)
58
- * will load the parameters for the action from the incoming payload
59
- * This functionality is to allow having a POST counterpart to any GET requests that require long query strings, and for which the client cannot use a payload bodies (i.e,. Browser JS clients cannot send payload on GET requests).
60
- * Performance improvement:
61
- * Cache praxis associations' computation for ActiveRecord (so no communication with AR or DB happens after that)
62
- * Performance improvement: Use OJ as the (faster) default JSON renderer.
63
- * Introduce batch computation of resource attributes: This defines an optional DSL (`batch_computed`) to enable easier calculation of expensive attributes that can be calculated much more efficiently in group:
64
- * The new DSL takes an attribute name (Symbol), options and an implementation block that is able to get a list of resource instances (a hash of them, indexed by id) and perform the computation for all of them at once.
65
- * Defining an attribute this way, resources can be used to be much more efficiently to calculate values that can be retrieved much more efficiently in bulk, and/or that depend on other resources of the same type to do so (i.e., things that to calculate that attribute for one single resource can be greatly amortized by doing it for many).
66
- * The provided block to calculate the value of the attribute for a collection of resources of the same type is stored as a method inside an inner module of the resource class called BatchProcessors
67
- * The class level method is callable through `::BatchProcessors.<property_name>(rows_by_id: xxx)`. The rows_by_id: parameter has resource 'ids' as keys, and the resource instances themselves a values
68
- * By default an instance method of the same `<property_name>` name will also be created, with a default implementation that will call the `BatchProcessor.<property_name>` with only its instance id and instance, and will return only its result from it.
69
- * If creating the helper instance method is not desired, one can pass `with_instance_method: false` when defining the batched_computed block. This might be necessary if we want to define the method ourselves, or in cases where the resource itself has an 'id' property that is not called 'id' (in which case the implementation would not be correct as it uses the `id` property of the resource). If that's the case, disable the creation, and add your own instance method that uses the defined BatchProcessor method passing the right parameters.
70
- * It is also possible to query which attributes for a resource class are batch computed. This is done through .batched_attributes (which returns and array of symbol names)
71
- * NOTE: Defining batch_computed attributes needs to be done before finalization
74
+
75
+ - Improve surfacing of requirement attributes in Structs for OpenApi generated documentation
76
+ - Introduction of a new dsl `enable_large_params_proxy_action` for GET verb action definitions. When used, two things will happen:
77
+ - A new POST verb equivalent action will be defined:
78
+ - It will have a `payload` matching the shape of the original GET's params (with the exception of any param that was originally in the URL)
79
+ - By default, the route for this new POST request is gonna have the same URL as the original GET action, but appending `/actions/<action_name>` to it. This can be customized by passing the path with the `at:` parameter of the DSL. I.e., `enable_large_params_proxy_action at: /actions/myspecialname` will change the generated path (can use the `//...` syntax to not include the prefix defined for the endpoint). NOTE: this route needs to be compatible with any params that might be defined for the URL (i.e., `:id` and such).
80
+ - This action will be fully visible and fully documented in the API generated docs. However, it will not need to have a corresponding controller implementation since it will special-forward it to the original GET action switching the parameters for the payload.
81
+ - Specifically, upon receiving a request to the POST equivalent action, Praxis will detect it is a special action and will:
82
+ - use directly the original action (i.e., will do the before/after filters and call the controller's method)
83
+ - will load the parameters for the action from the incoming payload
84
+ - This functionality is to allow having a POST counterpart to any GET requests that require long query strings, and for which the client cannot use a payload bodies (i.e,. Browser JS clients cannot send payload on GET requests).
85
+ - Performance improvement:
86
+ - Cache praxis associations' computation for ActiveRecord (so no communication with AR or DB happens after that)
87
+ - Performance improvement: Use OJ as the (faster) default JSON renderer.
88
+ - Introduce batch computation of resource attributes: This defines an optional DSL (`batch_computed`) to enable easier calculation of expensive attributes that can be calculated much more efficiently in group:
89
+ - The new DSL takes an attribute name (Symbol), options and an implementation block that is able to get a list of resource instances (a hash of them, indexed by id) and perform the computation for all of them at once.
90
+ - Defining an attribute this way, resources can be used to be much more efficiently to calculate values that can be retrieved much more efficiently in bulk, and/or that depend on other resources of the same type to do so (i.e., things that to calculate that attribute for one single resource can be greatly amortized by doing it for many).
91
+ - The provided block to calculate the value of the attribute for a collection of resources of the same type is stored as a method inside an inner module of the resource class called BatchProcessors
92
+ - The class level method is callable through `::BatchProcessors.<property_name>(rows_by_id: xxx)`. The rows_by_id: parameter has resource 'ids' as keys, and the resource instances themselves a values
93
+ - By default an instance method of the same `<property_name>` name will also be created, with a default implementation that will call the `BatchProcessor.<property_name>` with only its instance id and instance, and will return only its result from it.
94
+ - If creating the helper instance method is not desired, one can pass `with_instance_method: false` when defining the batched_computed block. This might be necessary if we want to define the method ourselves, or in cases where the resource itself has an 'id' property that is not called 'id' (in which case the implementation would not be correct as it uses the `id` property of the resource). If that's the case, disable the creation, and add your own instance method that uses the defined BatchProcessor method passing the right parameters.
95
+ - It is also possible to query which attributes for a resource class are batch computed. This is done through .batched_attributes (which returns and array of symbol names)
96
+ - NOTE: Defining batch_computed attributes needs to be done before finalization
72
97
 
73
98
  ## 2.0.pre.24
74
- Assorted set of fixes and cleanup:
75
- * better forwarding signature for query methods
76
- * Fix the way with which to decide how to wrap an association (based on Enumerable isn't right, as Hashes are Enumerable as well). Wrapping decision
77
- is now made based on the association type, and not the shape of the resulting type.
78
- * Built handling of some multivalue and/or fuzzy matching cases in filtering params
79
- * unrestrict mustermann's dependent version
80
- * Support options and even passing a full type (instead of a block) in signature definitions (TypedMethods for resources)
99
+
100
+ Assorted set of fixes and cleanup:
101
+
102
+ - better forwarding signature for query methods
103
+ - Fix the way with which to decide how to wrap an association (based on Enumerable isn't right, as Hashes are Enumerable as well). Wrapping decision
104
+ is now made based on the association type, and not the shape of the resulting type.
105
+ - Built handling of some multivalue and/or fuzzy matching cases in filtering params
106
+ - unrestrict mustermann's dependent version
107
+ - Support options and even passing a full type (instead of a block) in signature definitions (TypedMethods for resources)
81
108
 
82
109
  ## 2.0.pre.22
83
- * Small fix in OpenAPI doc generation, which would detect and report more output types, even if they are only defined within the
84
- children of anonymous types.
110
+
111
+ - Small fix in OpenAPI doc generation, which would detect and report more output types, even if they are only defined within the
112
+ children of anonymous types.
85
113
 
86
114
  ## 2.0.pre.22
87
- * Introduced Resource callbacks (an includeable concern). Callbacks allow you to define methods or blocks to be executed `before`, `after` or `around` any existing method in the resource. Class-level callbacks are defined with `self.xxxxx`. These methods will be executed within the instance of the resource (i.e., in the same context of the original) and must be defined with the same parameter signature. For around methods, only blocks can be used, and to call the original (inner) one, one needs to yield.
88
- * Introduced QueryMethods for resources (an includeable concern). QueryMethods expose handy querying methods (`.get`, `.get!`, `.all`, `.first` and `.last` ) which will reach into the underlying ORM (i.e., right now, only ActiveModelCompat is supported) to perform the desired loading of data (and subsequent wrapping of results in resource instances).
89
- * For ActiveRecord `.get` takes a condition hash that will translate to `.find_by`, and `.all` gets a condition hash that will translate to `.where`.
90
- * `.get!` is a `.get` but that will raise a `Praxis::Mapper::ResourceNotFound` exception if nothing was found.
91
- * There is an `.including(<spec>)` function that can be use to preload the underlying associations. I.e., the `<spec>` argument will translate to `.includes(<spec>)` in ActiveRecord.
92
- * Introduced method signatures and validations for resources.
93
- * One can define a method signature with the `signature(<name>)` stanza, passing a block defining Attributor parameters. For instance method signatures, the `<name>` is just a symbol with the name of the method. For class level methods use a string, and prepend `self.` to it (i.e., `self.create`).
94
- * Signatures can only work for methods that either have a single argument (taken as a whole hash), or that have only keyword arguments (i.e., no mixed args and kwargs). It would be basically impossible to validate that combo against an Attributor Struct.
95
- * The calls to typed methods will be intercepted (using an around callback), and the incoming parameters will be validated against the Attributor Struct defined in the siguature, coerced if necessary and passed onto the original method. If the incoming parameters fail validation, a `IncompatibleTypeForMethodArguments` exception will be thrown.
115
+
116
+ - Introduced Resource callbacks (an includeable concern). Callbacks allow you to define methods or blocks to be executed `before`, `after` or `around` any existing method in the resource. Class-level callbacks are defined with `self.xxxxx`. These methods will be executed within the instance of the resource (i.e., in the same context of the original) and must be defined with the same parameter signature. For around methods, only blocks can be used, and to call the original (inner) one, one needs to yield.
117
+ - Introduced QueryMethods for resources (an includeable concern). QueryMethods expose handy querying methods (`.get`, `.get!`, `.all`, `.first` and `.last` ) which will reach into the underlying ORM (i.e., right now, only ActiveModelCompat is supported) to perform the desired loading of data (and subsequent wrapping of results in resource instances).
118
+ - For ActiveRecord `.get` takes a condition hash that will translate to `.find_by`, and `.all` gets a condition hash that will translate to `.where`.
119
+ - `.get!` is a `.get` but that will raise a `Praxis::Mapper::ResourceNotFound` exception if nothing was found.
120
+ - There is an `.including(<spec>)` function that can be use to preload the underlying associations. I.e., the `<spec>` argument will translate to `.includes(<spec>)` in ActiveRecord.
121
+ - Introduced method signatures and validations for resources.
122
+ - One can define a method signature with the `signature(<name>)` stanza, passing a block defining Attributor parameters. For instance method signatures, the `<name>` is just a symbol with the name of the method. For class level methods use a string, and prepend `self.` to it (i.e., `self.create`).
123
+ - Signatures can only work for methods that either have a single argument (taken as a whole hash), or that have only keyword arguments (i.e., no mixed args and kwargs). It would be basically impossible to validate that combo against an Attributor Struct.
124
+ - The calls to typed methods will be intercepted (using an around callback), and the incoming parameters will be validated against the Attributor Struct defined in the siguature, coerced if necessary and passed onto the original method. If the incoming parameters fail validation, a `IncompatibleTypeForMethodArguments` exception will be thrown.
96
125
 
97
126
  ## 2.0.pre.21
98
- * Fix nullable attribute in OpenApi generation
127
+
128
+ - Fix nullable attribute in OpenApi generation
129
+
99
130
  ## 2.0.pre.20
100
- * Changed the behavior of dev-mode when validate_responses. Now they return a 500 status code (instead of a 400) but with the same validation error format body.
101
- * validate_responses is meant to catch the application returning non-compliant responses for development only. As such, a 500 is much more appropriate and clear, as the validation is done on the behavior of the server, and not on the information sent by the client (i.e., it is a server problem, not reacting the way the API is defined)
102
- * Introduced a method to reload a Resouce (.reload), which will clear the memoized values and call record.reload as well
103
- * Open API Generation enhancements:
104
- * Fixed type discovery (where some types wouldn't be included in the output)
105
- * Changed the generation to output named types into components, and use `$ref` to point to them whenever appropriate
106
- * Report nullable attributes
131
+
132
+ - Changed the behavior of dev-mode when validate_responses. Now they return a 500 status code (instead of a 400) but with the same validation error format body.
133
+ - validate_responses is meant to catch the application returning non-compliant responses for development only. As such, a 500 is much more appropriate and clear, as the validation is done on the behavior of the server, and not on the information sent by the client (i.e., it is a server problem, not reacting the way the API is defined)
134
+ - Introduced a method to reload a Resouce (.reload), which will clear the memoized values and call record.reload as well
135
+ - Open API Generation enhancements:
136
+ - Fixed type discovery (where some types wouldn't be included in the output)
137
+ - Changed the generation to output named types into components, and use `$ref` to point to them whenever appropriate
138
+ - Report nullable attributes
139
+
107
140
  ## 2.0.pre.19
108
- * Introduced a new DSL for the `FilteringParams` type that allows filters for common attributes in your Media Types:
109
- * The new `any` DSL allows you to define which final leaf attribute to always allow, and with which operators and/or fuzzy restrictions.
110
- * For example, you can add `any updated_at, using: ['>','<']` which would allow the type to accept filters like `updated_at>2000-01-01`, or any existing nested fields like `posts.comments.updated_at>2000-01-01`
111
- * Note that the path of attributes passed in, will still need to exist and will be validated. Also, you still need to make sure that you have the right `filters_mapping` defined in your resources.
112
- * Changed `filters_mapping` to allow implicitly any filter path that is a valid representation of existing columns and associations. I.e., you do not have to explicitly define long nested filters that correspond to the same underlying path of associations and columns.
141
+
142
+ - Introduced a new DSL for the `FilteringParams` type that allows filters for common attributes in your Media Types:
143
+ - The new `any` DSL allows you to define which final leaf attribute to always allow, and with which operators and/or fuzzy restrictions.
144
+ - For example, you can add `any updated_at, using: ['>','<']` which would allow the type to accept filters like `updated_at>2000-01-01`, or any existing nested fields like `posts.comments.updated_at>2000-01-01`
145
+ - Note that the path of attributes passed in, will still need to exist and will be validated. Also, you still need to make sure that you have the right `filters_mapping` defined in your resources.
146
+ - Changed `filters_mapping` to allow implicitly any filter path that is a valid representation of existing columns and associations. I.e., you do not have to explicitly define long nested filters that correspond to the same underlying path of associations and columns.
147
+
113
148
  ## 2.0.pre.18
114
- * Upgraded to newest Attributor, which cleans up the required: true semantics to only work on keys, and introduces null: true for nullability of values (independent from presence of keys or not)
115
- * Fixed a selector generator bug that would occur when using deep nested resource dependencies as strings 'foo.bar.baz.bam'. In this cases only partial tracking of relationships would be built, which could cause to not fully eager load DB queries.
149
+
150
+ - Upgraded to newest Attributor, which cleans up the required: true semantics to only work on keys, and introduces null: true for nullability of values (independent from presence of keys or not)
151
+ - Fixed a selector generator bug that would occur when using deep nested resource dependencies as strings 'foo.bar.baz.bam'. In this cases only partial tracking of relationships would be built, which could cause to not fully eager load DB queries.
152
+
116
153
  ## 2.0.pre.17
117
- * Changed the Parameter Filtering to use left outer joins (and extra conditions), to allow for the proper results when OR clauses are involved in certain configurations.
118
- * Built support for allowing filtering directly on associations using `!` and `!!` operators. This allows to filter results where
119
- there are no associated rows (`!!`) or if there are some associated rows (`!`)
120
- * Allow implicit definition of `filters_mapping` for filter names that match top-level associations of the model (i.e., like we do for the columns)
154
+
155
+ - Changed the Parameter Filtering to use left outer joins (and extra conditions), to allow for the proper results when OR clauses are involved in certain configurations.
156
+ - Built support for allowing filtering directly on associations using `!` and `!!` operators. This allows to filter results where
157
+ there are no associated rows (`!!`) or if there are some associated rows (`!`)
158
+ - Allow implicit definition of `filters_mapping` for filter names that match top-level associations of the model (i.e., like we do for the columns)
159
+
121
160
  ## 2.0.pre.16
122
161
 
123
- * Updated `Resource.property` signature to only accept known named arguments (`dependencies` and `though` at this time) to spare anyone else from going insane wondering why their `depednencies` aren't working.
124
- * Fixed issue with Filtering Params, that occurred with using the ! or !! operators on String-typed fields.
162
+ - Updated `Resource.property` signature to only accept known named arguments (`dependencies` and `though` at this time) to spare anyone else from going insane wondering why their `depednencies` aren't working.
163
+ - Fixed issue with Filtering Params, that occurred with using the ! or !! operators on String-typed fields.
125
164
 
126
165
  ## 2.0.pre.14
127
166
 
128
- * More encoding/decoding robustness for filters.
129
- * Specs for how to encode filters are now properly defined by:
130
- * The "value" of the filters query string needs to be URI encoded (like any other query string value). This encoding is subject to the normal rules, and therefore "could" leave some of the URI unreserved characters (i.e., 'markers') unencoded depending on the client (Section 2.2 of https://tools.ietf.org/html/rfc2396).
131
- * The "values" for any of the conditions in the contents of the filters, however, will need to be properly "escaped" as well (prior to URL-encoding the whole syntax string itself like described above). This means that any match value needs to ensure that it has (at least) "(",")","|","&" and "," escaped as they are reserved characters for the filter expression syntax. For example, if I want to search for a name with value "Rocket&(Pants)", I need to first compose the syntax by: "name=<escaped Rocket&(Pants)>, which is "name=Rocket%26%28Pants%29" and then, just URI encode that query string value for the filters parameter in the URL like any other. For example: "filters=name%3DRocket%2526%2528Pants%2529"
132
- * When using a multi-match (csv-separated) list of values, you need to escape each of the values as well, leaving the 'comma' unescape, as that's part of the syntax. Then uri-encode it all for the filters query string parameter value like above.
133
- * Now, one can properly differentiate between fuzzy query prefix/postfix, and the literal data to search for (which can be or include '*'). Report that multi-matches (i.e., csv separated values for a single field, which translate into "IN" clauses) is not allowed if fuzzy matches are received (need to use multiple OR clauses for it).
167
+ - More encoding/decoding robustness for filters.
168
+ - Specs for how to encode filters are now properly defined by:
169
+ - The "value" of the filters query string needs to be URI encoded (like any other query string value). This encoding is subject to the normal rules, and therefore "could" leave some of the URI unreserved characters (i.e., 'markers') unencoded depending on the client (Section 2.2 of https://tools.ietf.org/html/rfc2396).
170
+ - The "values" for any of the conditions in the contents of the filters, however, will need to be properly "escaped" as well (prior to URL-encoding the whole syntax string itself like described above). This means that any match value needs to ensure that it has (at least) "(",")","|","&" and "," escaped as they are reserved characters for the filter expression syntax. For example, if I want to search for a name with value "Rocket&(Pants)", I need to first compose the syntax by: "name=<escaped Rocket&(Pants)>, which is "name=Rocket%26%28Pants%29" and then, just URI encode that query string value for the filters parameter in the URL like any other. For example: "filters=name%3DRocket%2526%2528Pants%2529"
171
+ - When using a multi-match (csv-separated) list of values, you need to escape each of the values as well, leaving the 'comma' unescape, as that's part of the syntax. Then uri-encode it all for the filters query string parameter value like above.
172
+ - Now, one can properly differentiate between fuzzy query prefix/postfix, and the literal data to search for (which can be or include '\*'). Report that multi-matches (i.e., csv separated values for a single field, which translate into "IN" clauses) is not allowed if fuzzy matches are received (need to use multiple OR clauses for it).
134
173
 
135
174
  ## 2.0.pre.13
136
175
 
137
- * Fix filters parser regression, which would incorrectly decode url-encoded values
176
+ - Fix filters parser regression, which would incorrectly decode url-encoded values
138
177
 
139
178
  ## 2.0.pre.12
140
179
 
141
- * Rebuilt API filters to support a much richer syntax. One can now use ANDs and ORs (with ANDs having order precedence), as well as group them with parenthesis. The same individual filter operands are supported. For example: 'email=*@gmail.com&(friends.first_name=Joe*,Patty|friends.last_name=Smith)
180
+ - Rebuilt API filters to support a much richer syntax. One can now use ANDs and ORs (with ANDs having order precedence), as well as group them with parenthesis. The same individual filter operands are supported. For example: 'email=_@gmail.com&(friends.first_name=Joe_,Patty|friends.last_name=Smith)
142
181
 
143
182
  ## 2.0.pre.11
144
183
 
@@ -152,7 +191,7 @@ there are no associated rows (`!!`) or if there are some associated rows (`!`)
152
191
  - Simple, but pervasive breaking change: Rename `ResourceDefinition` to `EndpointDefinition` (but same functionality).
153
192
  - Remove all deprecated features (and raise error describing it's not supported yet)
154
193
  - Remove `Links` and `LinkBuilder`. Those seem unnecessary from a Framework point of view as they aren't clear most
155
- applications would benefit from it. Applications can choose to add that functionality on their own if so desire.
194
+ applications would benefit from it. Applications can choose to add that functionality on their own if so desire.
156
195
  - Rebuilt app generators: for new empty app, and example app.
157
196
  - Updated default layout to match new naming structure and more concepts commonly necessary for normal applications.
158
197
  - Completely removed the native Praxis API documentation browser in lieu of OpenAPI 3.x standards, and reDoc.
@@ -190,6 +229,7 @@ applications would benefit from it. Applications can choose to add that function
190
229
  - Added support for OpenAPI 3.x document generation. Consider this in Beta state, although it is fairly close to feature complete.
191
230
 
192
231
  ## 2.0.pre.4
232
+
193
233
  - Reworked the field selection DB query generation to support full tree of eager loaded dependencies
194
234
  - Built support for both ActiveRecord and Sequel gems
195
235
  - Selected DB fields will include/map the defined resource properties and will always include any necessary fields on both sides of the joins for the given associations.
data/Gemfile CHANGED
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
4
4
  gemspec
5
5
 
6
- gem 'rubocop'
6
+ gem "rubocop"
7
7
 
8
8
  group :test do
9
- gem 'builder'
9
+ gem "builder"
10
10
 
11
- gem 'link_header'
12
- gem 'oj'
13
- gem 'parslet'
11
+ gem "link_header"
12
+ gem "oj"
13
+ gem "parslet"
14
14
  end
data/bin/praxis CHANGED
@@ -47,6 +47,8 @@ path_to_loader = format('%<path>s/tasks/loader.thor', path: path_to_praxis)
47
47
  load path_to_loader
48
48
 
49
49
  class PraxisGenerator < Thor
50
+ SCAFFOLD_CONFIG_FILE = "#{Dir.pwd}/.praxis_scaffold"
51
+
50
52
  # Include a few fake thor action descriptions (for the rake tasks above) so they can show up in the same usage messages
51
53
  desc 'routes [json]', 'Prints the route table of the application. Defaults to table format, but can produce json'
52
54
  def routes; end
@@ -91,7 +93,9 @@ class PraxisGenerator < Thor
91
93
  # Cannot use the argument below or it will apply to all commands (the action in the class has it)
92
94
  # argument :collection_name, required: false
93
95
  # The options, however, since they're optional are fine (But need to be duplicated from the class :( )
94
- option :version, required: false, default: '1',
96
+ option :base, required: false,
97
+ desc: 'Module name to enclose all generated files. Empty by default. You can pass things like MyApp, or MyApp::SubModule'
98
+ option :version, required: false,
95
99
  desc: 'Version string for the API endpoint. This also dictates the directory structure (i.e., v1/endpoints/...))'
96
100
  option :design, type: :boolean, default: true,
97
101
  desc: 'Include the Endpoint and MediaType files for the collection'
@@ -120,6 +124,25 @@ class PraxisGenerator < Thor
120
124
  opts = { orm: options[:model] }
121
125
  opts[:orm] = 'activerecord' if opts[:orm] == 'model' # value is model param passed by no value
122
126
  ::PraxisGen::Model.new([collection_name.singularize], opts).invoke(:g)
127
+ self.class.save_scaffolding_config
128
+ end
129
+
130
+ # Read and pass the hash around in the class, so callers can read and modify it if desired
131
+ # Final contents will be saved at the end of scaffolding generation
132
+ def self.scaffold_config
133
+ return @current_config if @current_config
134
+
135
+ @current_config = File.exist?(SCAFFOLD_CONFIG_FILE) ? JSON.parse(File.read(SCAFFOLD_CONFIG_FILE), symbolize_names: true) : {}
136
+ end
137
+
138
+ def self.save_scaffolding_config
139
+ if File.exist?(SCAFFOLD_CONFIG_FILE)
140
+ contents_from_file = JSON.parse(File.read(SCAFFOLD_CONFIG_FILE), symbolize_names: true)
141
+ return if contents_from_file == @current_config
142
+ end
143
+
144
+ puts "Saving new scaffolding config into #{SCAFFOLD_CONFIG_FILE}"
145
+ File.write(SCAFFOLD_CONFIG_FILE, JSON.pretty_generate(scaffold_config))
123
146
  end
124
147
 
125
148
  # Initially, the idea was to build some quick model generator, but I think it's better to keep it
@@ -0,0 +1,16 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop"
6
+ gem "activerecord", "> 4", "< 7"
7
+ gem "sequel", "~> 5"
8
+
9
+ group :test do
10
+ gem "builder"
11
+ gem "link_header"
12
+ gem "oj"
13
+ gem "parslet"
14
+ end
15
+
16
+ gemspec path: "../"