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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +1 -3
  5. data/CHANGELOG.md +33 -0
  6. data/TODO.md +1 -4
  7. data/bin/praxis +67 -12
  8. data/lib/praxis.rb +10 -3
  9. data/lib/praxis/action_definition.rb +15 -13
  10. data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
  11. data/lib/praxis/api_general_info.rb +1 -1
  12. data/lib/praxis/application.rb +6 -2
  13. data/lib/praxis/blueprint.rb +357 -0
  14. data/lib/praxis/bootloader.rb +9 -3
  15. data/lib/praxis/bootloader_stages/environment.rb +16 -13
  16. data/lib/praxis/collection.rb +1 -11
  17. data/lib/praxis/config_hash.rb +44 -0
  18. data/lib/praxis/docs/{openapi → open_api}/info_object.rb +18 -10
  19. data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
  20. data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
  21. data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +0 -0
  22. data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +0 -0
  23. data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
  24. data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
  25. data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
  26. data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
  27. data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
  28. data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
  29. data/lib/praxis/docs/open_api_generator.rb +91 -6
  30. data/lib/praxis/endpoint_definition.rb +273 -0
  31. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +182 -58
  32. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
  33. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +47 -56
  34. data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +153 -0
  35. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
  36. data/lib/praxis/extensions/field_expansion.rb +3 -36
  37. data/lib/praxis/extensions/pagination.rb +5 -32
  38. data/lib/praxis/extensions/pagination/ordering_params.rb +1 -1
  39. data/lib/praxis/extensions/pagination/pagination_params.rb +6 -4
  40. data/lib/praxis/field_expander.rb +90 -0
  41. data/lib/praxis/finalizable.rb +34 -0
  42. data/lib/praxis/mapper/active_model_compat.rb +4 -0
  43. data/lib/praxis/mapper/resource.rb +18 -2
  44. data/lib/praxis/mapper/selector_generator.rb +2 -1
  45. data/lib/praxis/mapper/sequel_compat.rb +7 -0
  46. data/lib/praxis/media_type.rb +3 -68
  47. data/lib/praxis/plugin_concern.rb +1 -1
  48. data/lib/praxis/plugins/mapper_plugin.rb +24 -15
  49. data/lib/praxis/plugins/pagination_plugin.rb +34 -4
  50. data/lib/praxis/renderer.rb +88 -0
  51. data/lib/praxis/request.rb +1 -1
  52. data/lib/praxis/resource_definition.rb +2 -311
  53. data/lib/praxis/response_definition.rb +2 -10
  54. data/lib/praxis/response_template.rb +3 -3
  55. data/lib/praxis/router.rb +2 -2
  56. data/lib/praxis/routing_config.rb +1 -1
  57. data/lib/praxis/tasks/api_docs.rb +17 -64
  58. data/lib/praxis/tasks/routes.rb +1 -1
  59. data/lib/praxis/types/media_type_common.rb +1 -11
  60. data/lib/praxis/version.rb +1 -1
  61. data/praxis.gemspec +0 -1
  62. data/spec/functional_spec.rb +5 -9
  63. data/spec/praxis/action_definition_spec.rb +12 -20
  64. data/spec/praxis/blueprint_spec.rb +373 -0
  65. data/spec/praxis/bootloader_spec.rb +10 -2
  66. data/spec/praxis/collection_spec.rb +0 -13
  67. data/spec/praxis/config_hash_spec.rb +64 -0
  68. data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +37 -64
  69. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +249 -168
  70. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
  71. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +190 -8
  72. data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +140 -0
  73. data/spec/praxis/extensions/field_expansion_spec.rb +5 -24
  74. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
  75. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
  76. data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
  77. data/spec/praxis/field_expander_spec.rb +149 -0
  78. data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
  79. data/spec/praxis/media_type_identifier_spec.rb +5 -4
  80. data/spec/praxis/media_type_spec.rb +4 -93
  81. data/spec/praxis/renderer_spec.rb +188 -0
  82. data/spec/praxis/response_definition_spec.rb +0 -31
  83. data/spec/praxis/response_spec.rb +1 -1
  84. data/spec/praxis/router_spec.rb +8 -8
  85. data/spec/praxis/routing_config_spec.rb +3 -3
  86. data/spec/spec_app/app/controllers/instances.rb +13 -7
  87. data/spec/spec_app/design/media_types/instance.rb +1 -19
  88. data/spec/spec_app/design/media_types/volume.rb +1 -1
  89. data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
  90. data/spec/spec_app/design/resources/instances.rb +5 -8
  91. data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
  92. data/spec/spec_app/design/resources/volumes.rb +1 -1
  93. data/spec/support/spec_authorization_plugin.rb +1 -1
  94. data/spec/support/spec_blueprints.rb +72 -0
  95. data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
  96. data/spec/support/spec_media_types.rb +6 -26
  97. data/tasks/thor/app.rb +8 -34
  98. data/tasks/thor/example.rb +51 -285
  99. data/tasks/thor/model.rb +40 -0
  100. data/tasks/thor/scaffold.rb +117 -0
  101. data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
  102. data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
  103. data/tasks/thor/templates/generator/empty_app/README.md +1 -1
  104. data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
  105. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
  106. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
  107. data/tasks/thor/templates/generator/empty_app/config/environment.rb +26 -17
  108. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
  109. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
  110. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
  111. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
  112. data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
  113. data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
  114. data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
  115. data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
  116. data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
  117. data/tasks/thor/templates/generator/example_app/Rakefile +61 -0
  118. data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
  119. data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
  120. data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
  121. data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +11 -0
  122. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +25 -0
  123. data/tasks/thor/templates/generator/example_app/config.ru +30 -0
  124. data/tasks/thor/templates/generator/example_app/config/environment.rb +41 -0
  125. data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +12 -0
  126. data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
  127. data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
  128. data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
  129. data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +21 -0
  130. data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +20 -0
  131. data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
  132. data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
  133. data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
  134. data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
  135. data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
  136. data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
  137. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
  138. data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
  139. data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
  140. metadata +64 -136
  141. data/lib/api_browser/.bowerrc +0 -3
  142. data/lib/api_browser/.editorconfig +0 -21
  143. data/lib/api_browser/Gruntfile.js +0 -581
  144. data/lib/api_browser/app/index.html +0 -59
  145. data/lib/api_browser/app/js/app.js +0 -48
  146. data/lib/api_browser/app/js/controllers/action.js +0 -47
  147. data/lib/api_browser/app/js/controllers/controller.js +0 -10
  148. data/lib/api_browser/app/js/controllers/menu.js +0 -93
  149. data/lib/api_browser/app/js/controllers/trait.js +0 -10
  150. data/lib/api_browser/app/js/controllers/type.js +0 -24
  151. data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
  152. data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
  153. data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
  154. data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
  155. data/lib/api_browser/app/js/directives/highlight.js +0 -14
  156. data/lib/api_browser/app/js/directives/menu_item.js +0 -59
  157. data/lib/api_browser/app/js/directives/no_container.js +0 -8
  158. data/lib/api_browser/app/js/directives/readable_list.js +0 -87
  159. data/lib/api_browser/app/js/directives/request_examples.js +0 -31
  160. data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
  161. data/lib/api_browser/app/js/directives/url.js +0 -15
  162. data/lib/api_browser/app/js/factories/Configuration.js +0 -12
  163. data/lib/api_browser/app/js/factories/Documentation.js +0 -61
  164. data/lib/api_browser/app/js/factories/Example.js +0 -51
  165. data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
  166. data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
  167. data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
  168. data/lib/api_browser/app/js/factories/template_for.js +0 -128
  169. data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
  170. data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
  171. data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
  172. data/lib/api_browser/app/js/filters/header_info.js +0 -9
  173. data/lib/api_browser/app/js/filters/is_empty.js +0 -8
  174. data/lib/api_browser/app/js/filters/markdown.js +0 -6
  175. data/lib/api_browser/app/js/filters/resource_name.js +0 -5
  176. data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
  177. data/lib/api_browser/app/sass/modules/_body.scss +0 -40
  178. data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
  179. data/lib/api_browser/app/sass/modules/_header.scss +0 -10
  180. data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
  181. data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
  182. data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
  183. data/lib/api_browser/app/sass/modules/_table.scss +0 -13
  184. data/lib/api_browser/app/sass/praxis.scss +0 -70
  185. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
  186. data/lib/api_browser/app/views/action.html +0 -97
  187. data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
  188. data/lib/api_browser/app/views/controller.html +0 -55
  189. data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
  190. data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
  191. data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
  192. data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
  193. data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
  194. data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
  195. data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
  196. data/lib/api_browser/app/views/directives/menu_item.html +0 -8
  197. data/lib/api_browser/app/views/directives/url.html +0 -3
  198. data/lib/api_browser/app/views/examples/general.html +0 -26
  199. data/lib/api_browser/app/views/home.html +0 -5
  200. data/lib/api_browser/app/views/layout.html +0 -8
  201. data/lib/api_browser/app/views/menu.html +0 -42
  202. data/lib/api_browser/app/views/navbar.html +0 -9
  203. data/lib/api_browser/app/views/trait.html +0 -13
  204. data/lib/api_browser/app/views/type.html +0 -6
  205. data/lib/api_browser/app/views/type/details.html +0 -33
  206. data/lib/api_browser/app/views/types/embedded/array.html +0 -2
  207. data/lib/api_browser/app/views/types/embedded/default.html +0 -12
  208. data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
  209. data/lib/api_browser/app/views/types/embedded/links.html +0 -11
  210. data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
  211. data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
  212. data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
  213. data/lib/api_browser/app/views/types/label/link.html +0 -1
  214. data/lib/api_browser/app/views/types/label/primitive.html +0 -1
  215. data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
  216. data/lib/api_browser/app/views/types/label/type.html +0 -1
  217. data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
  218. data/lib/api_browser/app/views/types/main/array.html +0 -22
  219. data/lib/api_browser/app/views/types/main/default.html +0 -23
  220. data/lib/api_browser/app/views/types/main/hash.html +0 -23
  221. data/lib/api_browser/app/views/types/standalone/array.html +0 -3
  222. data/lib/api_browser/app/views/types/standalone/default.html +0 -18
  223. data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
  224. data/lib/api_browser/bower_template.json +0 -41
  225. data/lib/api_browser/package-lock.json +0 -7110
  226. data/lib/api_browser/package.json +0 -43
  227. data/lib/praxis/docs/generator.rb +0 -243
  228. data/lib/praxis/docs/link_builder.rb +0 -30
  229. data/lib/praxis/links.rb +0 -135
  230. data/lib/praxis/types/multipart.rb +0 -109
  231. data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
  232. data/spec/api_browser/factories/configuration_spec.js +0 -32
  233. data/spec/api_browser/factories/documentation_spec.js +0 -100
  234. data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
  235. data/spec/api_browser/factories/template_for_spec.js +0 -67
  236. data/spec/api_browser/filters/attribute_name_spec.js +0 -23
  237. data/spec/praxis/types/multipart_spec.rb +0 -112
  238. data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
  239. data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
  240. data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
  241. data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
  242. data/tasks/thor/templates/generator/empty_app/docs/styles.scss +0 -3
@@ -29,7 +29,7 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
29
29
  :id # We always load the primary keys
30
30
  ]
31
31
  end
32
- let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(ActiveBookResource,selector_fields) }
32
+ let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(ActiveBookResource,selector_fields).selectors }
33
33
  let(:debug){ false }
34
34
 
35
35
  subject(:selector) {described_class.new(query: query, selectors: selector_node, debug: debug) }
@@ -64,7 +64,7 @@ describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
64
64
  ]
65
65
  end
66
66
 
67
- let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(SequelSimpleResource,selector_fields) }
67
+ let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(SequelSimpleResource,selector_fields).selectors }
68
68
  subject {described_class.new(query: query, selectors: selector_node, debug: debug) }
69
69
 
70
70
  context 'generate' do
@@ -103,7 +103,7 @@ class ActiveBookResource < ActiveBaseResource
103
103
 
104
104
  filters_mapping(
105
105
  id: :id,
106
- category_uuid: :category_uuid,
106
+ # category_uuid: :category_uuid #NOTE: we do not need to define same-name-mappings if they exist
107
107
  'fake_nested.name': 'simple_name',
108
108
  'name': 'simple_name',
109
109
  'name_is_not': lambda do |spec| # Silly way to use a proc, but good enough for testing
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+
4
+ describe Praxis::FieldExpander do
5
+ let(:field_expander) { Praxis::FieldExpander.new }
6
+ let(:expanded_person_default_fieldset) do
7
+ # The only ones that are not already leaves is the full name, which can expand to first, last
8
+ PersonBlueprint.default_fieldset.merge!(full_name: {first: true, last: true})
9
+ end
10
+ let(:expanded_address_default_fieldset) do
11
+ # AddressBlueprint' default fieldset already has all attributes as leaves
12
+ AddressBlueprint.default_fieldset
13
+ end
14
+
15
+ context 'expanding attributes of a PersonBlueprint blueprint' do
16
+ it 'with fields=true, expands all fields on the default fieldset' do
17
+ expect(field_expander.expand(PersonBlueprint, true)).to eq(expanded_person_default_fieldset)
18
+ end
19
+
20
+ it 'expands for a subset of the direct fields' do
21
+ expect(field_expander.expand(PersonBlueprint, name: true)).to eq(name: true)
22
+ end
23
+
24
+ it 'raises when trying to expand fields that do not exist in the type' do
25
+ expect do
26
+ field_expander.expand(PersonBlueprint, name: true, foobar: true)
27
+ end.to raise_error(/.*:foobar.*do not exist in PersonBlueprint/)
28
+ end
29
+
30
+ context 'given inlined struct attribtue' do
31
+ it 'expands for a sub-struct' do
32
+ expect(field_expander.expand(PersonBlueprint, parents: true)).to eq(parents: { mother: true, father: true })
33
+ end
34
+
35
+ it 'expands for a subset of a substruct' do
36
+ expect(field_expander.expand(PersonBlueprint, parents: { mother: true })).to eq(parents: { mother: true })
37
+ end
38
+ end
39
+
40
+ context 'given a non-inlined struct sub attribute' do
41
+ it 'expands a specific subattribute of a struct' do
42
+ expect(field_expander.expand(PersonBlueprint, full_name: { first: true })).to eq(full_name: { first: true })
43
+ end
44
+
45
+ it 'fully expands collections properly by simply listing subfields' do
46
+ expect(field_expander.expand(PersonBlueprint, prior_addresses: true)).to eq(prior_addresses: expanded_address_default_fieldset)
47
+ end
48
+ end
49
+
50
+ context 'given a attribute that is also a blueprint' do
51
+ it 'expands to default fieldset for an attribute that is also blueprint' do
52
+ expect(field_expander.expand(PersonBlueprint, address: true)).to eq(address: expanded_address_default_fieldset)
53
+ end
54
+
55
+ it 'expands to default fieldset for a subset of fields of an attribute that is a blueprint' do
56
+ expect(field_expander.expand(PersonBlueprint, address: { resident: true })).to eq(address: { resident: expanded_person_default_fieldset })
57
+ end
58
+ end
59
+
60
+ context 'collection attributes' do
61
+ it 'expands subfields by simply listing subfields the same as structs/blueprints' do
62
+ expect(field_expander.expand(PersonBlueprint, prior_addresses: { state: true })).to eq(prior_addresses: { state: true })
63
+ end
64
+ end
65
+ end
66
+
67
+ it 'expands for for a primitive type' do
68
+ expect(field_expander.expand(String)).to eq(true)
69
+ end
70
+
71
+ it 'expands for an Attributor::Model' do
72
+ expect(field_expander.expand(FullName)).to eq(first: true, last: true)
73
+ end
74
+
75
+ it 'expands for a Blueprint' do
76
+ expect(field_expander.expand(PersonBlueprint, parents: true)).to eq(parents: { father: true, mother: true })
77
+ end
78
+
79
+ it 'expands for an Attributor::Collection of an Attrbutor::Model' do
80
+ expect(field_expander.expand(Attributor::Collection.of(FullName))).to eq(first: true, last: true)
81
+ end
82
+
83
+ it 'expands for an Attributor::Collection of a Blueprint' do
84
+ expected = { name: true, resident: { full_name: { first: true, last: true } } }
85
+ expect(field_expander.expand(Attributor::Collection.of(AddressBlueprint), name: true, resident: { full_name: true })).to eq(expected)
86
+ end
87
+
88
+ it 'also expands array-wrapped field hashes for collections' do
89
+ expected = { name: true, resident: { full_name: { first: true, last: true } } }
90
+ expect(field_expander.expand(Attributor::Collection.of(AddressBlueprint), name: true, resident: { full_name: true })).to eq(expected)
91
+ end
92
+
93
+ it 'expands for an Attributor::Collection of a primitive type' do
94
+ expect(field_expander.expand(Attributor::Collection.of(String))).to eq(true)
95
+ end
96
+
97
+ context 'expanding a two-dimensional collection' do
98
+ it 'expands the fields discarding the collection nexting nesting' do
99
+ matrix_type = Attributor::Collection.of(Attributor::Collection.of(FullName))
100
+ expect(field_expander.expand(matrix_type)).to eq(first: true, last: true)
101
+ end
102
+ end
103
+
104
+ context 'circular expansions' do
105
+ it 'preserve field object identity for circular references' do
106
+ result = field_expander.expand(PersonBlueprint, address: {resident: true}, work_address: {resident: true})
107
+ expect(result[:address][:resident]).to be(result[:work_address][:resident])
108
+ end
109
+
110
+ context 'with collections of Blueprints' do
111
+ it 'still preserves object identity' do
112
+ result = field_expander.expand(PersonBlueprint, address: {resident: true}, prior_addresses: {resident: true})
113
+ expect(result[:address][:resident]).to be(result[:prior_addresses][:resident])
114
+ end
115
+ end
116
+ end
117
+
118
+ it 'optimizes duplicate field expansions' do
119
+ expect(field_expander.expand(FullName, true)).to be(field_expander.expand(FullName, true))
120
+ end
121
+
122
+ context 'expanding hash attributes' do
123
+ let(:type) do
124
+ Class.new(Attributor::Model) do
125
+ attributes do
126
+ attribute :name, String
127
+ attribute :simple_hash, Hash
128
+ attribute :keyed_hash, Hash do
129
+ key :foo, String
130
+ key :bar, String
131
+ end
132
+ attribute :some_struct do
133
+ attribute :something, String
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ it 'expands' do
140
+ expected = {
141
+ name: true,
142
+ simple_hash: true,
143
+ keyed_hash: { foo: true, bar: true },
144
+ some_struct: { something: true }
145
+ }
146
+ expect(field_expander.expand(type, true)).to eq(expected)
147
+ end
148
+ end
149
+ end
@@ -8,7 +8,7 @@ describe Praxis::Mapper::SelectorGenerator do
8
8
  context '#add' do
9
9
  let(:resource) { SimpleResource }
10
10
  shared_examples 'a proper selector' do
11
- it { expect(generator.add(resource, fields).dump).to be_deep_equal selectors }
11
+ it { expect(generator.add(resource, fields).selectors.dump).to be_deep_equal selectors }
12
12
  end
13
13
 
14
14
  context 'basic combos' do
@@ -42,10 +42,11 @@ describe Praxis::MediaTypeIdentifier do
42
42
  expect(described_class.new(unquoted_example).parameters['encoding']).to eq('martian')
43
43
  end
44
44
 
45
- it 'handles quoted semicolons in values' do
46
- pending("need to stop using a regexp to do a context-free parser's job")
47
- expect(described_class.new(tricky_example).parameters['sauce']).to eq('yes; absolutely')
48
- end
45
+ # TODO: Maybe that's something we want to support in the future? seems a big hammer for such a thing
46
+ # it 'handles quoted semicolons in values' do
47
+ # pending("need to stop using a regexp to do a context-free parser's job")
48
+ # expect(described_class.new(tricky_example).parameters['sauce']).to eq('yes; absolutely')
49
+ # end
49
50
 
50
51
 
51
52
  end
@@ -22,7 +22,6 @@ describe Praxis::MediaType do
22
22
 
23
23
  subject(:address) { Address.new(resource) }
24
24
 
25
-
26
25
  context 'attributes' do
27
26
  its(:id) { should eq(1) }
28
27
  its(:name) { should eq('Home') }
@@ -30,112 +29,24 @@ describe Praxis::MediaType do
30
29
  end
31
30
 
32
31
 
33
- context 'loading' do
34
- it do
35
- Person.load({id: 1})
36
- Person.load(owner_resource)
37
-
38
- end
39
- end
40
-
41
-
42
32
  context 'accessor methods' do
43
33
  subject(:address_klass) { address.class }
44
34
 
45
35
  context '#identifier' do
46
- context 'in praxis v1.0 and beyond' do
47
- it 'should be a kind of Praxis::MediaTypeIdentifier' do
48
- pending('interface-breaking change') if Praxis::VERSION =~ /^0/
49
- expect(subject.identifier).to be_kind_of(Praxis::MediaTypeIdentifier)
50
- end
36
+ it 'should be a kind of Praxis::MediaTypeIdentifier' do
37
+ expect(subject.identifier).to be_kind_of(Praxis::MediaTypeIdentifier)
51
38
  end
52
39
  end
53
40
 
54
41
  its(:description) { should be_kind_of(String) }
55
-
56
42
  end
57
43
 
58
44
  context "rendering" do
59
- subject(:output) { address.render(view: :default) }
45
+ subject(:output) { address.render }
60
46
 
61
47
  its([:id]) { should eq(address.id) }
62
48
  its([:name]) { should eq(address.name) }
63
- its([:owner]) { should eq(Person.dump(owner_resource, view: :default)) }
49
+ its([:owner]) { should eq(Person.dump(owner_resource)) }
64
50
  its([:fields]) { should eq(address.fields.dump ) }
65
-
66
- end
67
-
68
- context 'describing' do
69
-
70
- subject(:described){ Address.describe }
71
-
72
- its(:keys) { should match_array( [:attributes, :description, :display_name, :family, :id, :identifier, :key, :name, :views, :requirements] ) }
73
- its([:attributes]) { should be_kind_of(::Hash) }
74
- its([:description]) { should be_kind_of(::String) }
75
- its([:display_name]) { should be_kind_of(::String) }
76
- its([:family]) { should be_kind_of(::String) }
77
- its([:id]) { should be_kind_of(::String) }
78
- its([:name]) { should be_kind_of(::String) }
79
- its([:identifier]) { should be_kind_of(::String) }
80
- its([:key]) { should be_kind_of(::Hash) }
81
- its([:views]) { should be_kind_of(::Hash) }
82
-
83
- its([:description]) { should eq(Address.description) }
84
- its([:display_name]) { should eq(Address.display_name) }
85
- its([:family]) { should eq(Address.family) }
86
- its([:id]) { should eq(Address.id) }
87
- its([:name]) { should eq(Address.name) }
88
- its([:identifier]) { should eq(Address.identifier.to_s) }
89
- it 'should include the defined views' do
90
- expect( subject[:views].keys ).to match_array([:default, :master])
91
- end
92
- it 'should include the defined attributes' do
93
- expect( subject[:attributes].keys ).to match_array([:id, :name, :owner, :custodian, :residents, :residents_summary, :fields])
94
- end
95
- end
96
-
97
- context 'using blueprint caching' do
98
- it 'has specs'
99
- end
100
-
101
- context Praxis::MediaType::FieldResolver do
102
- let(:expander) { Praxis::FieldExpander }
103
- let(:user_view) { User.views[:default] }
104
-
105
- let(:fields) { expander.expand(user_view) }
106
-
107
- let(:field_resolver) { Praxis::MediaType::FieldResolver }
108
-
109
- subject(:output) { field_resolver.resolve(User,fields) }
110
-
111
- context 'resolving collections' do
112
- let(:fields) { {:id=>true, :posts=>[{:href=>true}]}}
113
- it 'strips arrays from the incoming fields' do
114
- expect(output).to eq(id: true, posts: {href: true})
115
- end
116
-
117
- it 'supports multi-dimensional collections' do
118
- fields = {
119
- id: true,
120
- post_matrix:[[{title: true, href: true}]]
121
- }
122
- output = field_resolver.resolve(User,fields)
123
- expect(output).to eq(id: true, post_matrix:{href: true, title: true})
124
- end
125
-
126
- it 'supports nesting structs and arrays collections' do
127
- fields = {
128
- id: true,
129
- daily_posts: [
130
- {day: true, posts: [{id: true}]}
131
- ]
132
- }
133
- output = field_resolver.resolve(User,fields)
134
- expect(output).to eq(id: true, daily_posts:{day: true, posts: {id:true}})
135
- end
136
- end
137
-
138
51
  end
139
-
140
-
141
52
  end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+ require_relative '../spec_helper'
3
+
4
+ describe Praxis::Renderer do
5
+ let(:address) { AddressBlueprint.example }
6
+ let(:prior_addresses) { Array.new(2) { AddressBlueprint.example } }
7
+ let(:alias_one) { FullName.example }
8
+ let(:alias_two) { FullName.example }
9
+ let(:aliases) { [alias_one, alias_two] }
10
+ let(:metadata_hash) { { something: 'here' } }
11
+ let(:metadata) { Attributor::Hash.load(metadata_hash) }
12
+
13
+ let(:person) do
14
+ PersonBlueprint.example(
15
+ address: address,
16
+ email: nil,
17
+ prior_addresses: prior_addresses,
18
+ alive: false,
19
+ work_address: nil,
20
+ aliases: aliases,
21
+ metadata: metadata
22
+ )
23
+ end
24
+
25
+ let(:fields) do
26
+ {
27
+ name: true,
28
+ email: true,
29
+ full_name: { first: true, last: true },
30
+ address: {
31
+ state: true,
32
+ street: true,
33
+ resident: { name: true }
34
+ },
35
+ prior_addresses: { name: true },
36
+ work_address: true,
37
+ alive: true,
38
+ metadata: true,
39
+ aliases: true
40
+ }
41
+ end
42
+
43
+ let(:renderer) { Praxis::Renderer.new }
44
+
45
+ subject(:output) { renderer.render(person, fields) }
46
+
47
+ it 'renders existing attributes' do
48
+ expect(output.keys).to match_array([:name, :full_name, :alive, :address, :prior_addresses, :metadata, :aliases])
49
+
50
+ expect(output[:name]).to eq person.name
51
+ expect(output[:full_name]).to eq(first: person.full_name.first, last: person.full_name.last)
52
+ expect(output[:alive]).to be false
53
+
54
+ expect(output[:address]).to eq(state: person.address.state,
55
+ street: person.address.street,
56
+ resident: { name: person.address.resident.name })
57
+
58
+ expected_prior_addresses = prior_addresses.collect { |addr| { name: addr.name } }
59
+ expect(output[:prior_addresses]).to match_array(expected_prior_addresses)
60
+
61
+ expect(output[:aliases]).to match_array(aliases.collect(&:dump))
62
+ expect(output[:metadata]).to eq(metadata.dump)
63
+ end
64
+
65
+ context 'calls dump for non-Blueprint, but still Dumpable instances' do
66
+ it 'when rendering them in full as array members' do
67
+ expect(alias_one).to receive(:dump).and_call_original
68
+ expect(output[:aliases].first).to eq(first: alias_one.first, last: alias_one.last)
69
+ end
70
+ it 'when rendering them in full as leaf object' do
71
+ expect(metadata).to receive(:dump).and_call_original
72
+ expect(output[:metadata]).to eq(metadata_hash)
73
+ end
74
+ end
75
+
76
+ it 'does not render attributes with nil values' do
77
+ expect(output).to_not have_key(:email)
78
+ end
79
+
80
+ context 'with include_nil: true' do
81
+ let(:renderer) { Praxis::Renderer.new(include_nil: true) }
82
+ let(:address) { nil }
83
+
84
+ it 'renders attributes with nil values' do
85
+ expect(output).to have_key :email
86
+ expect(output[:email]).to be_nil
87
+
88
+ expect(output).to have_key :work_address
89
+ expect(output[:work_address]).to be_nil
90
+ end
91
+
92
+ it 'renders nil directly for nil subobjects' do
93
+ expect(output).to have_key :address
94
+ expect(output[:address]).to be_nil
95
+ end
96
+ end
97
+
98
+ context 'rendering a two-dimmensional collection' do
99
+ let(:names) { Array.new(9) { |i| AddressBlueprint.example(i.to_s, name: i.to_s) } }
100
+ let(:matrix_type) do
101
+ Attributor::Collection.of(Attributor::Collection.of(AddressBlueprint))
102
+ end
103
+
104
+ let(:matrix) { matrix_type.load(names.each_slice(3).collect { |slice| slice }) }
105
+
106
+ let(:fields) { { name: true } }
107
+
108
+ it 'renders with render_collection and per-element field spec' do
109
+ rendered = renderer.render(matrix, fields)
110
+ expect(rendered.flatten.collect { |r| r[:name] }).to eq((0..8).collect(&:to_s))
111
+ end
112
+
113
+ it 'renders with render and proper field spec' do
114
+ rendered = renderer.render(matrix, fields)
115
+ expect(rendered.flatten.collect { |r| r[:name] }).to eq((0..8).collect(&:to_s))
116
+ end
117
+ end
118
+
119
+ context 'rendering stuff that breaks badly' do
120
+ it 'does not break badly' do
121
+ expect{renderer.render(person, {tags: true})}.to_not raise_error
122
+ end
123
+ end
124
+
125
+ context 'caching rendered objects' do
126
+ let(:fields) { {full_name: true} }
127
+ it 'caches and returns identical results for the same field objects' do
128
+ expect(person).to receive(:full_name).once.and_call_original
129
+
130
+ render_1 = renderer.render(person, fields)
131
+ render_2 = renderer.render(person, fields)
132
+ expect(render_1).to be(render_2)
133
+ end
134
+ end
135
+
136
+ context 'rendering hashes' do
137
+ let(:fields) do
138
+ {
139
+ id: true,
140
+ hash: true
141
+ }
142
+ end
143
+
144
+ let(:data) { { id: 10, hash: { foo: 'bar' } } }
145
+ let(:object) { SimpleHash.load(data) }
146
+ let(:renderer) { Praxis::Renderer.new }
147
+
148
+ subject(:output) { renderer.render(object, fields) }
149
+
150
+ its([:id]) { should eq data[:id] }
151
+ its([:hash]) { should eq data[:hash] }
152
+ its([:hash]) { should be_kind_of(Hash) }
153
+ end
154
+
155
+ context 'rendering collections of hashes' do
156
+ let(:fields) do
157
+ {
158
+ id: true,
159
+ hash_collection: true
160
+ }
161
+ end
162
+
163
+ let(:data) { { id: 10, hash_collection: [{ foo: 'bar' }] } }
164
+ let(:object) { SimpleHashCollection.load(data) }
165
+ let(:renderer) { Praxis::Renderer.new }
166
+
167
+ subject(:output) { renderer.render(object, fields) }
168
+
169
+ its([:id]) { should eq data[:id] }
170
+ its([:hash_collection]) { should eq data[:hash_collection] }
171
+ its([:hash_collection]) { should be_kind_of(Array) }
172
+
173
+ it 'renders the hashes' do
174
+ expect(output[:hash_collection].first).to be_kind_of(Hash)
175
+ end
176
+ end
177
+
178
+ context 'rendering a Blueprint with fields true' do
179
+ let(:fields) do
180
+ {
181
+ name: true,
182
+ address: true
183
+ }
184
+ end
185
+
186
+ its([:address]) { should eq person.address.dump }
187
+ end
188
+ end