rest_framework 0.9.4 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +130 -0
  4. data/VERSION +1 -1
  5. data/app/views/layouts/rest_framework.html.erb +9 -183
  6. data/app/views/rest_framework/_breadcrumbs.html.erb +27 -0
  7. data/app/views/rest_framework/_head.html.erb +348 -190
  8. data/app/views/rest_framework/_header.html.erb +15 -0
  9. data/app/views/rest_framework/_heading.html.erb +10 -0
  10. data/app/views/rest_framework/_payloads.html.erb +36 -0
  11. data/app/views/rest_framework/_request_metadata.html.erb +16 -0
  12. data/app/views/rest_framework/_routes_and_forms.html.erb +52 -0
  13. data/app/views/rest_framework/head/_external.html.erb +7 -2
  14. data/app/views/rest_framework/header/_mode.html.erb +14 -0
  15. data/app/views/rest_framework/heading/_actions.html.erb +9 -0
  16. data/app/views/rest_framework/{_routes.html.erb → routes_and_forms/_routes.html.erb} +2 -2
  17. data/lib/rest_framework/controller_mixins/base.rb +11 -12
  18. data/lib/rest_framework/engine.rb +5 -3
  19. data/lib/rest_framework/filters/ransack.rb +6 -6
  20. data/lib/rest_framework/version.rb +0 -6
  21. data/lib/rest_framework.rb +25 -13
  22. data/vendor/assets/javascripts/rest_framework/external.min.js +1256 -0
  23. data/vendor/assets/stylesheets/rest_framework/{bootstrap-icons.css → external.min.css} +415 -1
  24. data/vendor/assets/stylesheets/rest_framework/{highlight-a11y-dark.css → highlight-a11y-dark.min.css} +1 -1
  25. data/vendor/assets/stylesheets/rest_framework/{highlight-a11y-light.css → highlight-a11y-light.min.css} +1 -1
  26. metadata +18 -35
  27. data/README.md +0 -1
  28. data/app/views/rest_framework/head/_shared.html +0 -164
  29. data/docs/CNAME +0 -1
  30. data/docs/Gemfile +0 -5
  31. data/docs/Gemfile.lock +0 -264
  32. data/docs/_config.yml +0 -19
  33. data/docs/_guide/1_routers.md +0 -110
  34. data/docs/_guide/2_controllers.md +0 -342
  35. data/docs/_guide/3_serializers.md +0 -60
  36. data/docs/_guide/4_filtering_and_ordering.md +0 -41
  37. data/docs/_guide/5_pagination.md +0 -21
  38. data/docs/_includes/anchor_headings.html +0 -144
  39. data/docs/_includes/external.html +0 -9
  40. data/docs/_includes/head.html +0 -155
  41. data/docs/_includes/header.html +0 -58
  42. data/docs/_includes/shared.html +0 -164
  43. data/docs/_layouts/default.html +0 -11
  44. data/docs/assets/images/favicon.ico +0 -0
  45. data/docs/index.md +0 -133
  46. data/vendor/assets/javascripts/rest_framework/bootstrap.js +0 -7
  47. data/vendor/assets/javascripts/rest_framework/highlight-json.js +0 -7
  48. data/vendor/assets/javascripts/rest_framework/highlight-xml.js +0 -29
  49. data/vendor/assets/javascripts/rest_framework/highlight.js +0 -1202
  50. data/vendor/assets/javascripts/rest_framework/neatjson.js +0 -8
  51. data/vendor/assets/javascripts/rest_framework/trix.js +0 -6
  52. data/vendor/assets/stylesheets/rest_framework/bootstrap.css +0 -6
  53. data/vendor/assets/stylesheets/rest_framework/trix.css +0 -410
  54. /data/app/views/rest_framework/{_html_form.html.erb → routes_and_forms/_html_form.html.erb} +0 -0
  55. /data/app/views/rest_framework/{_raw_form.html.erb → routes_and_forms/_raw_form.html.erb} +0 -0
  56. /data/app/views/rest_framework/{_route.html.erb → routes_and_forms/routes/_route.html.erb} +0 -0
@@ -0,0 +1,52 @@
1
+ <%
2
+ @is_model_controller = controller.class.included_modules.include?(RESTFramework::ModelControllerMixin)
3
+ %>
4
+ <div class="row">
5
+ <div>
6
+ <ul class="nav nav-tabs">
7
+ <li class="nav-item">
8
+ <a class="nav-link active" href="#tab-routes" data-bs-toggle="tab" role="tab">
9
+ Routes
10
+ </a>
11
+ </li>
12
+ <% @_rrf_form_routes_raw = @route_groups.values[0].select { |r|
13
+ r[:matches_params] && (
14
+ r[:verb].in?(["POST", "PUT", "PATCH"]) ||
15
+ (r[:verb] == "DELETE" && r[:action] == "destroy_all")
16
+ )
17
+ } %>
18
+ <% @_rrf_form_routes_html = @route_groups.values[0].select { |r|
19
+ r[:matches_params] && r[:verb].in?(["POST", "PUT", "PATCH"])
20
+ } %>
21
+ <% if @_rrf_form_routes_raw.present? %>
22
+ <li class="nav-item">
23
+ <a class="nav-link" href="#tabRawForm" data-bs-toggle="tab" role="tab">
24
+ Raw Form
25
+ </a>
26
+ </li>
27
+ <% end %>
28
+ <% if @_rrf_form_routes_html.present? && @is_model_controller %>
29
+ <li class="nav-item">
30
+ <a class="nav-link" href="#tabHtmlForm" data-bs-toggle="tab" role="tab">
31
+ HTML Form
32
+ </a>
33
+ </li>
34
+ <% end %>
35
+ </ul>
36
+ </div>
37
+ <div class="tab-content pt-2">
38
+ <div class="tab-pane fade show active" id="tab-routes" role="tabpanel">
39
+ <%= render partial: "rest_framework/routes_and_forms/routes" %>
40
+ </div>
41
+ <% if @_rrf_form_routes_raw.present? %>
42
+ <div class="tab-pane fade" id="tabRawForm" role="tabpanel">
43
+ <%= render partial: "rest_framework/routes_and_forms/raw_form" %>
44
+ </div>
45
+ <% end %>
46
+ <% if @_rrf_form_routes_html.present? && @is_model_controller %>
47
+ <div class="tab-pane fade" id="tabHtmlForm" role="tabpanel">
48
+ <%= render partial: "rest_framework/routes_and_forms/html_form" %>
49
+ </div>
50
+ <% end %>
51
+ </div>
52
+ </div>
@@ -1,9 +1,14 @@
1
1
  <% if RESTFramework.config.use_vendored_assets %>
2
- <% RESTFramework::EXTERNAL_ASSETS.each do |name, cfg| %>
2
+ <%= javascript_include_tag RESTFramework::EXTERNAL_JS_NAME, defer: true %>
3
+ <%= stylesheet_link_tag RESTFramework::EXTERNAL_CSS_NAME %>
4
+
5
+ <% RESTFramework::EXTERNAL_UNSUMMARIZED_ASSETS.each do |name, cfg| %>
3
6
  <% if cfg[:place] == "javascripts" %>
4
7
  <%= javascript_include_tag "rest_framework/#{name}", defer: true, **cfg[:extra_tag_attrs] %>
5
- <% else %>
8
+ <% elsif cfg[:place] == "stylesheets" %>
6
9
  <%= stylesheet_link_tag "rest_framework/#{name}", **cfg[:extra_tag_attrs] %>
10
+ <% else %>
11
+ <% raise "Unknown asset place." %>
7
12
  <% end %>
8
13
  <% end %>
9
14
  <% else %>
@@ -0,0 +1,14 @@
1
+ <div class="dropdown float-end" id="rrfModeComponent">
2
+ <button class="btn btn-dark dropdown-toggle rounded-0 bg-black" style="border-color: black" data-bs-toggle="dropdown"></button>
3
+ <div class="rrf-mode dropdown-menu dropdown-menu-end py-0 rounded-0" style="font-size: .8em; min-width: 0">
4
+ <button class="dropdown-item text-end" data-rrf-mode-value="system">
5
+ System<i class="bi bi-circle-half ms-2"></i>
6
+ </button>
7
+ <button class="dropdown-item text-end" data-rrf-mode-value="light">
8
+ Light<i class="bi bi-sun-fill ms-2"></i>
9
+ </button>
10
+ <button class="dropdown-item text-end" data-rrf-mode-value="dark">
11
+ Dark<i class="bi bi-moon-stars-fill ms-2"></i>
12
+ </button>
13
+ </div>
14
+ </div>
@@ -0,0 +1,9 @@
1
+ <div style="float: right">
2
+ <% if @route_groups.values[0].any? { |r| r[:matches_path] && r[:verb] == "DELETE" && r[:action] == "destroy" } %>
3
+ <button type="button" class="btn btn-danger" onclick="rrfDelete(this)">DELETE</button>
4
+ <% end %>
5
+ <% if @route_groups.values[0].any? { |r| r[:matches_path] && r[:verb] == "OPTIONS" } %>
6
+ <button type="button" class="btn btn-primary" onclick="rrfOptions(this)">OPTIONS</button>
7
+ <% end %>
8
+ <button type="button" class="btn btn-primary" onclick="rrfGet(this)">GET</button>
9
+ </div>
@@ -10,7 +10,7 @@
10
10
  <%# Render first group of routes directly. %>
11
11
  <tbody>
12
12
  <% @route_groups.values[0].each do |route| %>
13
- <%= render partial: "rest_framework/route", locals: {route: route} %>
13
+ <%= render partial: "rest_framework/routes_and_forms/routes/route", locals: {route: route} %>
14
14
  <% end %>
15
15
  </tbody>
16
16
  <%# Render any other groups under dropdowns. %>
@@ -20,7 +20,7 @@
20
20
  </tr>
21
21
  <tbody id="route-group-<%= index %>" class="collapse">
22
22
  <% route_group.each do |route| %>
23
- <%= render partial: "rest_framework/route", locals: {route: route} %>
23
+ <%= render partial: "rest_framework/routes_and_forms/routes/route", locals: {route: route} %>
24
24
  <% end %>
25
25
  </tbody>
26
26
  <% end %>
@@ -146,6 +146,9 @@ module RESTFramework::BaseControllerMixin
146
146
 
147
147
  base.extend(ClassMethods)
148
148
 
149
+ # By default, the layout should be set to `rest_framework`.
150
+ base.layout("rest_framework")
151
+
149
152
  # Add class attributes (with defaults) unless they already exist.
150
153
  RRF_BASE_CONFIG.each do |a, default|
151
154
  next if base.respond_to?(a)
@@ -267,8 +270,8 @@ module RESTFramework::BaseControllerMixin
267
270
 
268
271
  # Render a browsable API for `html` format, along with basic `json`/`xml` formats, and with
269
272
  # support or passing custom `kwargs` to the underlying `render` calls.
270
- def api_response(payload, html_kwargs: nil, **kwargs)
271
- html_kwargs ||= {}
273
+ def api_response(payload, **kwargs)
274
+ html_kwargs = kwargs.delete(:html_kwargs) || {}
272
275
  json_kwargs = kwargs.delete(:json_kwargs) || {}
273
276
  xml_kwargs = kwargs.delete(:xml_kwargs) || {}
274
277
 
@@ -300,12 +303,10 @@ module RESTFramework::BaseControllerMixin
300
303
  format.xml { head(kwargs[:status] || :no_content) } if self.serialize_to_xml
301
304
  else
302
305
  format.json {
303
- jkwargs = kwargs.merge(json_kwargs)
304
- render(json: payload, layout: false, **jkwargs)
306
+ render(json: payload, **kwargs.merge(json_kwargs))
305
307
  } if self.serialize_to_json
306
308
  format.xml {
307
- xkwargs = kwargs.merge(xml_kwargs)
308
- render(xml: payload, layout: false, **xkwargs)
309
+ render(xml: payload, **kwargs.merge(xml_kwargs))
309
310
  } if self.serialize_to_xml
310
311
  # TODO: possibly support more formats here if supported?
311
312
  end
@@ -323,13 +324,11 @@ module RESTFramework::BaseControllerMixin
323
324
  @route_props, @route_groups = RESTFramework::Utils.get_routes(
324
325
  Rails.application.routes, request
325
326
  )
326
- hkwargs = kwargs.merge(html_kwargs)
327
327
  begin
328
- render(**hkwargs)
329
- rescue ActionView::MissingTemplate # Fallback to `rest_framework` layout.
330
- hkwargs[:layout] = "rest_framework"
331
- hkwargs[:html] = ""
332
- render(**hkwargs)
328
+ render(**kwargs.merge(html_kwargs))
329
+ rescue ActionView::MissingTemplate
330
+ # A view is not required, so just use `html: ""`.
331
+ render(html: "", layout: true, **kwargs.merge(html_kwargs))
333
332
  end
334
333
  }
335
334
  end
@@ -2,9 +2,11 @@ class RESTFramework::Engine < Rails::Engine
2
2
  initializer "rest_framework.assets" do
3
3
  config.after_initialize do |app|
4
4
  if RESTFramework.config.use_vendored_assets
5
- app.config.assets.precompile += RESTFramework::EXTERNAL_ASSETS.keys.map do |name|
6
- "rest_framework/#{name}"
7
- end
5
+ app.config.assets.precompile += [
6
+ RESTFramework::EXTERNAL_CSS_NAME,
7
+ RESTFramework::EXTERNAL_JS_NAME,
8
+ *RESTFramework::EXTERNAL_UNSUMMARIZED_ASSETS.keys.map { |name| "rest_framework/#{name}" },
9
+ ]
8
10
  end
9
11
  end
10
12
  end
@@ -9,15 +9,15 @@ class RESTFramework::RansackFilter < RESTFramework::BaseFilter
9
9
 
10
10
  # Determine if `distinct` is determined by query param.
11
11
  if distinct_query_param = @controller.ransack_distinct_query_param
12
- distinct_from_query = ActiveRecord::Type::Boolean.new.cast(
13
- @controller.request.query_parameters[distinct_query_param],
14
- )
15
- unless distinct_from_query.nil?
16
- distinct = distinct_from_query
12
+ if distinct_query = @controller.request.query_parameters[distinct_query_param].presence
13
+ distinct_from_query = ActiveRecord::Type::Boolean.new.cast(distinct_query)
14
+ unless distinct_from_query.nil?
15
+ distinct = distinct_from_query
16
+ end
17
17
  end
18
18
  end
19
19
 
20
- return data.ransack(q, @controller.ransack_options).result(distinct: distinct)
20
+ return data.ransack(q, @controller.ransack_options || {}).result(distinct: distinct)
21
21
  end
22
22
 
23
23
  return data
@@ -17,12 +17,6 @@ module RESTFramework
17
17
  rescue SystemCallError
18
18
  end
19
19
 
20
- # If that fails, then try to get a plain commit SHA from git.
21
- unless skip_git
22
- version = `git describe --dirty --always`&.strip
23
- return "0.#{version}" unless !version || version.empty?
24
- end
25
-
26
20
  # No VERSION file, so version is unknown.
27
21
  return "0.unknown"
28
22
  end
@@ -20,65 +20,73 @@ module RESTFramework
20
20
  destroy_all: :delete,
21
21
  }.freeze
22
22
 
23
+ # We put most vendored external assets into these files to make precompilation and serving faster.
24
+ EXTERNAL_CSS_NAME = "rest_framework/external.min.css"
25
+ EXTERNAL_JS_NAME = "rest_framework/external.min.js"
26
+
27
+ # We should always add the `.min` extension prefix even if the assets are not minified, to avoid
28
+ # sprockets minifying the assets. We target propshaft, so we want these assets to just be passed
29
+ # through.
23
30
  # rubocop:disable Layout/LineLength
24
31
  EXTERNAL_ASSETS = {
25
32
  # Bootstrap
26
- "bootstrap.css" => {
33
+ "bootstrap.min.css" => {
27
34
  url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/css/bootstrap.min.css",
28
35
  sri: "sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp",
29
36
  },
30
- "bootstrap.js" => {
37
+ "bootstrap.min.js" => {
31
38
  url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/js/bootstrap.bundle.min.js",
32
39
  sri: "sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N",
33
40
  },
34
41
 
35
42
  # Bootstrap Icons
36
- "bootstrap-icons.css" => {
43
+ "bootstrap-icons.min.css" => {
37
44
  url: "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.min.css",
38
45
  inline_fonts: true,
39
46
  },
40
47
 
41
48
  # Highlight.js
42
- "highlight.js" => {
49
+ "highlight.min.js" => {
43
50
  url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js",
44
51
  sri: "sha512-bgHRAiTjGrzHzLyKOnpFvaEpGzJet3z4tZnXGjpsCcqOnAH6VGUx9frc5bcIhKTVLEiCO6vEhNAgx5jtLUYrfA==",
45
52
  },
46
- "highlight-json.js" => {
53
+ "highlight-json.min.js" => {
47
54
  url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/json.min.js",
48
55
  sri: "sha512-0xYvyncS9OLE7GOpNBZFnwyh9+bq4HVgk4yVVYI678xRvE22ASicF1v6fZ1UiST+M6pn17MzFZdvVCI3jTHSyw==",
49
56
  },
50
- "highlight-xml.js" => {
57
+ "highlight-xml.min.js" => {
51
58
  url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/xml.min.js",
52
59
  sri: "sha512-5zBcw+OKRkaNyvUEPlTSfYylVzgpi7KpncY36b0gRudfxIYIH0q0kl2j26uCUB3YBRM6ytQQEZSgRg+ZlBTmdA==",
53
60
  },
54
- "highlight-a11y-dark.css" => {
61
+ "highlight-a11y-dark.min.css" => {
55
62
  url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/a11y-dark.min.css",
56
63
  sri: "sha512-Vj6gPCk8EZlqnoveEyuGyYaWZ1+jyjMPg8g4shwyyNlRQl6d3L9At02ZHQr5K6s5duZl/+YKMnM3/8pDhoUphg==",
57
64
  extra_tag_attrs: {class: "rrf-dark-mode"},
58
65
  },
59
- "highlight-a11y-light.css" => {
66
+ "highlight-a11y-light.min.css" => {
60
67
  url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/a11y-light.min.css",
61
68
  sri: "sha512-WDk6RzwygsN9KecRHAfm9HTN87LQjqdygDmkHSJxVkVI7ErCZ8ZWxP6T8RvBujY1n2/E4Ac+bn2ChXnp5rnnHA==",
62
69
  extra_tag_attrs: {class: "rrf-light-mode"},
63
70
  },
64
71
 
65
72
  # NeatJSON
66
- "neatjson.js" => {
73
+ "neatjson.min.js" => {
67
74
  url: "https://cdn.jsdelivr.net/npm/neatjson@0.10.5/javascript/neatjson.min.js",
68
75
  exclude_from_docs: true,
69
76
  },
70
77
 
71
78
  # Trix
72
- "trix.css" => {
79
+ "trix.min.css" => {
73
80
  url: "https://unpkg.com/trix@2.0.0/dist/trix.css",
74
81
  exclude_from_docs: true,
75
82
  },
76
- "trix.js" => {
83
+ "trix.min.js" => {
77
84
  url: "https://unpkg.com/trix@2.0.0/dist/trix.umd.min.js",
78
85
  exclude_from_docs: true,
79
86
  },
80
87
  }.map { |name, cfg|
81
- if File.extname(name) == ".js"
88
+ ext = File.extname(name)
89
+ if ext == ".js"
82
90
  cfg[:place] = "javascripts"
83
91
  cfg[:extra_tag_attrs] ||= {}
84
92
  cfg[:tag_attrs] = {
@@ -90,7 +98,7 @@ module RESTFramework
90
98
  **cfg[:extra_tag_attrs],
91
99
  }
92
100
  cfg[:tag] = ActionController::Base.helpers.tag.script(**cfg[:tag_attrs])
93
- else
101
+ elsif ext == ".css"
94
102
  cfg[:place] = "stylesheets"
95
103
  cfg[:extra_tag_attrs] ||= {}
96
104
  cfg[:tag_attrs] = {
@@ -101,12 +109,16 @@ module RESTFramework
101
109
  **cfg[:extra_tag_attrs],
102
110
  }
103
111
  cfg[:tag] = ActionController::Base.helpers.tag.link(**cfg[:tag_attrs])
112
+ else
113
+ raise "Unknown asset extension: #{ext}."
104
114
  end
105
115
 
106
116
  [name, cfg]
107
117
  }.to_h.freeze
108
118
  # rubocop:enable Layout/LineLength
109
119
 
120
+ EXTERNAL_UNSUMMARIZED_ASSETS = EXTERNAL_ASSETS.select { |_, cfg| cfg[:extra_tag_attrs].present? }
121
+
110
122
  # Global configuration should be kept minimal, as controller-level configurations allows multiple
111
123
  # APIs to be defined to behave differently.
112
124
  class Config