administrate 0.18.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/administrate/components/select.js +3 -0
  3. data/app/assets/stylesheets/administrate/components/_field-unit.scss +7 -0
  4. data/app/controllers/administrate/application_controller.rb +7 -4
  5. data/app/controllers/concerns/administrate/punditize.rb +43 -21
  6. data/app/views/administrate/application/_collection.html.erb +2 -3
  7. data/app/views/administrate/application/_form.html.erb +19 -4
  8. data/app/views/administrate/application/_index_header.html.erb +1 -1
  9. data/app/views/administrate/application/_navigation.html.erb +1 -1
  10. data/app/views/administrate/application/_pagination.html.erb +1 -1
  11. data/app/views/administrate/application/edit.html.erb +1 -1
  12. data/app/views/administrate/application/new.html.erb +1 -1
  13. data/app/views/administrate/application/show.html.erb +19 -11
  14. data/app/views/fields/has_many/_show.html.erb +2 -1
  15. data/app/views/fields/has_one/_form.html.erb +14 -4
  16. data/app/views/fields/has_one/_show.html.erb +18 -12
  17. data/app/views/fields/select/_form.html.erb +5 -18
  18. data/app/views/layouts/administrate/application.html.erb +1 -1
  19. data/config/locales/administrate.ja.yml +5 -5
  20. data/docs/authorization.md +18 -8
  21. data/docs/customizing_controller_actions.md +14 -0
  22. data/docs/customizing_dashboards.md +77 -14
  23. data/docs/getting_started.md +1 -1
  24. data/docs/guides/customising_search.md +1 -1
  25. data/lib/administrate/base_dashboard.rb +26 -4
  26. data/lib/administrate/field/associative.rb +11 -1
  27. data/lib/administrate/field/belongs_to.rb +5 -2
  28. data/lib/administrate/field/deferred.rb +1 -1
  29. data/lib/administrate/field/has_many.rb +20 -6
  30. data/lib/administrate/field/number.rb +2 -8
  31. data/lib/administrate/field/polymorphic.rb +2 -1
  32. data/lib/administrate/field/select.rb +19 -9
  33. data/lib/administrate/not_authorized_error.rb +3 -1
  34. data/lib/administrate/order.rb +49 -23
  35. data/lib/administrate/page/collection.rb +1 -0
  36. data/lib/administrate/page/form.rb +9 -8
  37. data/lib/administrate/page/show.rb +10 -2
  38. data/lib/administrate/resource_resolver.rb +2 -1
  39. data/lib/administrate/search.rb +1 -1
  40. data/lib/administrate/version.rb +1 -1
  41. data/lib/administrate/view_generator.rb +6 -1
  42. data/lib/administrate.rb +8 -4
  43. data/lib/generators/administrate/dashboard/dashboard_generator.rb +6 -1
  44. data/lib/generators/administrate/dashboard/templates/controller.rb.erb +2 -2
  45. data/lib/generators/administrate/install/install_generator.rb +6 -1
  46. data/lib/generators/administrate/routes/routes_generator.rb +11 -2
  47. data/lib/generators/administrate/test_record.rb +21 -0
  48. metadata +37 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6711a800e61649dffc4e96f9ddfaf3af21d364a1cfc659959dc5496357af262
4
- data.tar.gz: 3dc0046165d843a60359343d756b584cb7d5a8a505c5ba4c2858749b4aceba28
3
+ metadata.gz: eebcadb06ece9944279b1119e950922a388078a5c9ab5b95d6d8863c1e03994d
4
+ data.tar.gz: 96a697e2e10228a5f2772b1602985107f7ed428ca63d77c7d6e964a614659b9d
5
5
  SHA512:
6
- metadata.gz: 88099b1defc211784c017a82dc2a4f90d91599f664a768acb652dd5a9ac3358b00180bc1f7da7b072e26cb1412b967fb7c244096005ee782fb1719340a832f3b
7
- data.tar.gz: 2d01b4c1131ed1a7c2d9f70756c5d9b0b442abf846425c486ae0d9b5ee6d65c9d3e0e3f45b1d9864749c6901cc29cc90f3261c229ba8ba96e26ee552f92e9d88
6
+ metadata.gz: da130cb7cdcfd15321a653a59cfb499411f85324c19216097a6f4a76407268cc6691647e2ccdf86c367bd6c2e1491342324de89e22c9152eddbea8e6ba32b819
7
+ data.tar.gz: 8813f237457fa4e37abf8d5c350e4fb4f8f536583a6919764bc5c2abb0bc3b3fc436431b41e731722045179c45dcfef0d4c45d8c5534e7d26ee52d151252665f
@@ -0,0 +1,3 @@
1
+ $(function() {
2
+ $('.field-unit--select select').selectize({});
3
+ });
@@ -2,6 +2,7 @@
2
2
  @include administrate-clearfix;
3
3
  align-items: center;
4
4
  display: flex;
5
+ flex-wrap: wrap;
5
6
  margin-bottom: $base-spacing;
6
7
  position: relative;
7
8
  width: 100%;
@@ -25,6 +26,12 @@
25
26
  }
26
27
  }
27
28
 
29
+ .field-unit__hint {
30
+ font-size: 90%;
31
+ margin-left: calc(15% + 2rem);
32
+ width: 100%;
33
+ }
34
+
28
35
  .field-unit--nested {
29
36
  border: $base-border;
30
37
  margin-left: 7.5%;
@@ -40,10 +40,11 @@ module Administrate
40
40
  end
41
41
 
42
42
  def create
43
- resource = resource_class.new(resource_params)
43
+ resource = new_resource(resource_params)
44
44
  authorize_resource(resource)
45
45
 
46
46
  if resource.save
47
+ yield(resource) if block_given?
47
48
  redirect_to(
48
49
  after_resource_created_path(resource),
49
50
  notice: translate_with_resource("create.success"),
@@ -201,7 +202,7 @@ module Administrate
201
202
 
202
203
  def resource_params
203
204
  params.require(resource_class.model_name.param_key).
204
- permit(dashboard.permitted_attributes).
205
+ permit(dashboard.permitted_attributes(action_name)).
205
206
  transform_values { |v| read_param_value(v) }
206
207
  end
207
208
 
@@ -214,6 +215,8 @@ module Administrate
214
215
  end
215
216
  elsif data.is_a?(ActionController::Parameters)
216
217
  data.transform_values { |v| read_param_value(v) }
218
+ elsif data.is_a?(String) && data.blank?
219
+ nil
217
220
  else
218
221
  data
219
222
  end
@@ -265,8 +268,8 @@ module Administrate
265
268
  end
266
269
  helper_method :show_action?
267
270
 
268
- def new_resource
269
- resource_class.new
271
+ def new_resource(params = {})
272
+ resource_class.new(params)
270
273
  end
271
274
  helper_method :new_resource
272
275
 
@@ -2,37 +2,59 @@ module Administrate
2
2
  module Punditize
3
3
  if Object.const_defined?("Pundit")
4
4
  extend ActiveSupport::Concern
5
- include Pundit::Authorization
6
5
 
7
- included do
8
- private
6
+ if Pundit.const_defined?(:Authorization)
7
+ include Pundit::Authorization
8
+ else
9
+ include Pundit
10
+ end
9
11
 
10
- def scoped_resource
11
- policy_scope_admin super
12
- end
12
+ private
13
13
 
14
- def authorize_resource(resource)
15
- authorize resource
16
- end
14
+ def policy_namespace
15
+ []
16
+ end
17
17
 
18
- def authorized_action?(resource, action)
19
- Pundit.policy!(pundit_user, resource).send("#{action}?".to_sym)
20
- end
18
+ def scoped_resource
19
+ namespaced_scope = policy_namespace + [super]
20
+ policy_scope!(pundit_user, namespaced_scope)
21
21
  end
22
22
 
23
- private
23
+ def authorize_resource(resource)
24
+ namespaced_resource = policy_namespace + [resource]
25
+ authorize namespaced_resource
26
+ end
24
27
 
25
- # Like the policy_scope method in stock Pundit, but allows the 'resolve'
26
- # to be overridden by 'resolve_admin' for a different index scope in Admin
27
- # controllers.
28
- def policy_scope_admin(scope)
29
- ps = Pundit::PolicyFinder.new(scope).scope!.new(pundit_user, scope)
30
- if ps.respond_to? :resolve_admin
31
- ps.resolve_admin
28
+ def authorized_action?(resource, action)
29
+ namespaced_resource = policy_namespace + [resource]
30
+ policy = Pundit.policy!(pundit_user, namespaced_resource)
31
+ policy.send("#{action}?".to_sym)
32
+ end
33
+
34
+ def policy_scope!(user, scope)
35
+ policy_scope_class = Pundit::PolicyFinder.new(scope).scope!
36
+
37
+ begin
38
+ policy_scope = policy_scope_class.new(user, pundit_model(scope))
39
+ rescue ArgumentError
40
+ raise(Pundit::InvalidConstructorError,
41
+ "Invalid #<#{policy_scope_class}> constructor is called")
42
+ end
43
+
44
+ if policy_scope.respond_to? :resolve_admin
45
+ Administrate.deprecator.warn(
46
+ "Pundit policy scope `resolve_admin` method is deprecated. " +
47
+ "Please use a namespaced pundit policy instead.",
48
+ )
49
+ policy_scope.resolve_admin
32
50
  else
33
- ps.resolve
51
+ policy_scope.resolve
34
52
  end
35
53
  end
54
+
55
+ def pundit_model(record)
56
+ record.is_a?(Array) ? record.last : record
57
+ end
36
58
  end
37
59
  end
38
60
  end
@@ -27,15 +27,14 @@ to display a collection of resources in an HTML table.
27
27
  cell-label--<%= collection_presenter.ordered_html_class(attr_name) %>
28
28
  cell-label--<%= "#{collection_presenter.resource_name}_#{attr_name}" %>"
29
29
  scope="col"
30
- role="columnheader"
31
30
  aria-sort="<%= sort_order(collection_presenter.ordered_html_class(attr_name)) %>">
32
31
  <%= link_to(sanitized_order_params(page, collection_field_name).merge(
33
32
  collection_presenter.order_params_for(attr_name, key: collection_field_name)
34
33
  )) do %>
35
34
  <%= t(
36
35
  "helpers.label.#{collection_presenter.resource_name}.#{attr_name}",
37
- default: resource_class.human_attribute_name(attr_name),
38
- ).titleize %>
36
+ default: resource_class.human_attribute_name(attr_name).titleize,
37
+ ) %>
39
38
  <% if collection_presenter.ordered_by?(attr_name) %>
40
39
  <span class="cell-label__sort-indicator cell-label__sort-indicator--<%= collection_presenter.ordered_html_class(attr_name) %>">
41
40
  <svg aria-hidden="true">
@@ -33,10 +33,25 @@ and renders all form fields for a resource's editable attributes.
33
33
  </div>
34
34
  <% end %>
35
35
 
36
- <% page.attributes(controller.action_name).each do |attribute| -%>
37
- <div class="field-unit field-unit--<%= attribute.html_class %> field-unit--<%= requireness(attribute) %>">
38
- <%= render_field attribute, f: f %>
39
- </div>
36
+ <% page.attributes(controller.action_name).each do |title, attributes| -%>
37
+ <fieldset class="<%= "field-unit--nested" if title.present? %>">
38
+ <% if title.present? %>
39
+ <legend><%= t "helpers.label.#{f.object_name}.#{title}", default: title %></legend>
40
+ <% end %>
41
+
42
+ <% attributes.each do |attribute| %>
43
+ <div class="field-unit field-unit--<%= attribute.html_class %> field-unit--<%= requireness(attribute) %>">
44
+ <%= render_field attribute, f: f %>
45
+
46
+ <% hint_key = "administrate.field_hints.#{page.resource_name}.#{attribute.name}" %>
47
+ <% if I18n.exists?(hint_key) -%>
48
+ <div class="field-unit__hint">
49
+ <%= I18n.t(hint_key) %>
50
+ </div>
51
+ <% end -%>
52
+ </div>
53
+ <% end %>
54
+ </fieldset>
40
55
  <% end -%>
41
56
 
42
57
  <div class="form-actions">
@@ -2,7 +2,7 @@
2
2
  <%= display_resource_name(page.resource_name) %>
3
3
  <% end %>
4
4
 
5
- <header class="main-content__header" role="banner">
5
+ <header class="main-content__header">
6
6
  <h1 class="main-content__page-title" id="page-title">
7
7
  <%= content_for(:title) %>
8
8
  </h1>
@@ -7,7 +7,7 @@ for all resources in the admin dashboard,
7
7
  as defined by the routes in the `admin/` namespace
8
8
  %>
9
9
 
10
- <nav class="navigation" role="navigation">
10
+ <nav class="navigation">
11
11
  <%= link_to(t("administrate.navigation.back_to_app"), root_url, class: "button button--alt button--nav") if defined?(root_url) %>
12
12
 
13
13
  <% Administrate::Namespace.new(namespace).resources_with_index_route.each do |resource| %>
@@ -1 +1 @@
1
- <%= paginate resources, param_name: '_page' %>
1
+ <%= paginate resources, param_name: local_assigns.fetch(:param_name, "_page") %>
@@ -17,7 +17,7 @@ It displays a header, and renders the `_form` partial to do the heavy lifting.
17
17
 
18
18
  <% content_for(:title) { t("administrate.actions.edit_resource", name: page.page_title) } %>
19
19
 
20
- <header class="main-content__header" role="banner">
20
+ <header class="main-content__header">
21
21
  <h1 class="main-content__page-title">
22
22
  <%= content_for(:title) %>
23
23
  </h1>
@@ -22,7 +22,7 @@ to do the heavy lifting.
22
22
  ) %>
23
23
  <% end %>
24
24
 
25
- <header class="main-content__header" role="banner">
25
+ <header class="main-content__header">
26
26
  <h1 class="main-content__page-title">
27
27
  <%= content_for(:title) %>
28
28
  </h1>
@@ -18,7 +18,7 @@ as well as a link to its edit page.
18
18
 
19
19
  <% content_for(:title) { t("administrate.actions.show_resource", name: page.page_title) } %>
20
20
 
21
- <header class="main-content__header" role="banner">
21
+ <header class="main-content__header">
22
22
  <h1 class="main-content__page-title">
23
23
  <%= content_for(:title) %>
24
24
  </h1>
@@ -42,16 +42,24 @@ as well as a link to its edit page.
42
42
 
43
43
  <section class="main-content__body">
44
44
  <dl>
45
- <% page.attributes.each do |attribute| %>
46
- <dt class="attribute-label" id="<%= attribute.name %>">
47
- <%= t(
48
- "helpers.label.#{resource_name}.#{attribute.name}",
49
- default: page.resource.class.human_attribute_name(attribute.name),
50
- ) %>
51
- </dt>
52
-
53
- <dd class="attribute-data attribute-data--<%=attribute.html_class%>"
54
- ><%= render_field attribute, page: page %></dd>
45
+ <% page.attributes.each do |title, attributes| %>
46
+ <fieldset class="<%= "field-unit--nested" if title.present? %>">
47
+ <% if title.present? %>
48
+ <legend><%= t "helpers.label.#{page.resource_name}.#{title}", default: title %></legend>
49
+ <% end %>
50
+
51
+ <% attributes.each do |attribute| %>
52
+ <dt class="attribute-label" id="<%= attribute.name %>">
53
+ <%= t(
54
+ "helpers.label.#{resource_name}.#{attribute.name}",
55
+ default: page.resource.class.human_attribute_name(attribute.name),
56
+ ) %>
57
+ </dt>
58
+
59
+ <dd class="attribute-data attribute-data--<%=attribute.html_class%>"
60
+ ><%= render_field attribute, page: page %></dd>
61
+ <% end %>
62
+ </fieldset>
55
63
  <% end %>
56
64
  </dl>
57
65
  </section>
@@ -28,9 +28,10 @@ from the associated resource class's dashboard.
28
28
  page: page,
29
29
  resources: field.resources(page_number, order),
30
30
  table_title: field.name,
31
+ resource_class: field.associated_class,
31
32
  ) %>
32
33
  <% if field.more_than_limit? %>
33
- <%= paginate field.resources(page_number), param_name: "#{field.name}[page]" %>
34
+ <%= render("pagination", resources: field.resources(page_number), param_name: "#{field.name}[page]") %>
34
35
  <% end %>
35
36
 
36
37
  <% else %>
@@ -19,10 +19,20 @@ The form will be rendered as nested_from to parent relationship.
19
19
  <%= f.fields_for field.attribute, field.data || field.nested_form.resource.class.new do |has_one_f| %>
20
20
  <fieldset class="field-unit--nested">
21
21
  <legend><%= t "helpers.label.#{f.object_name}.#{field.name}", default: field.name.titleize %></legend>
22
- <% field.nested_form.attributes.each do |attribute| -%>
23
- <div class="field-unit field-unit--<%= attribute.html_class %>">
24
- <%= render_field attribute, f: has_one_f %>
25
- </div>
22
+ <% field.nested_form.attributes.each do |title, attributes| -%>
23
+
24
+ <fieldset class="<%= "field-unit--nested" if title.present? %>">
25
+ <% if title.present? %>
26
+ <legend><%= t "helpers.label.#{f.object_name}.#{title}", default: title %></legend>
27
+ <% end %>
28
+
29
+ <% attributes.each do |attribute| %>
30
+ <div class="field-unit field-unit--<%= attribute.html_class %>">
31
+ <%= render_field attribute, f: has_one_f %>
32
+ </div>
33
+ <% end %>
34
+ </fieldset>
35
+
26
36
  <% end -%>
27
37
  </fieldset>
28
38
  <% end %>
@@ -24,18 +24,24 @@ All show page attributes of has_one relationship would be rendered
24
24
  [namespace, field.data],
25
25
  ) %>
26
26
  </legend>
27
- <% field.nested_show.attributes.each do |attribute| -%>
28
- <div>
29
- <dt class="attribute-label">
30
- <%= t(
31
- "helpers.label.#{field.associated_class_name.underscore}.#{attribute.name}",
32
- default: attribute.name.titleize,
33
- ) %>
34
- </dt>
35
- <dd class="attribute-data attribute-data--<%= attribute.html_class %>">
36
- <%= render_field attribute, { page: page } %>
37
- </dd>
38
- </div>
27
+ <% field.nested_show.attributes.each do |title, attributes| -%>
28
+ <fieldset class="<%= "field-unit--nested" if title.present? %>">
29
+ <% if title.present? %>
30
+ <legend><%= t "helpers.label.#{namespace}.#{title}", default: title %></legend>
31
+ <% end %>
32
+
33
+ <% attributes.each do |attribute| %>
34
+ <dt class="attribute-label">
35
+ <%= t(
36
+ "helpers.label.#{field.associated_class_name.underscore}.#{attribute.name}",
37
+ default: attribute.name.titleize,
38
+ ) %>
39
+ </dt>
40
+ <dd class="attribute-data attribute-data--<%= attribute.html_class %>">
41
+ <%= render_field attribute, { page: page } %>
42
+ </dd>
43
+ <% end %>
44
+ </fieldset>
39
45
  <% end -%>
40
46
  </fieldset>
41
47
  <% end %>
@@ -19,27 +19,14 @@ to be displayed on a resource's edit form page.
19
19
  <%= f.label field.attribute %>
20
20
  </div>
21
21
  <div class="field-unit__field">
22
- <% if field.selectable_options.first&.is_a?(Array) %>
23
- <%= f.select(
22
+ <%=
23
+ f.select(
24
24
  field.attribute,
25
- options_from_collection_for_select(
25
+ options_for_select(
26
26
  field.selectable_options,
27
- :last,
28
- :first,
29
27
  field.data,
30
28
  ),
31
29
  include_blank: field.include_blank_option
32
- ) %>
33
- <% else %>
34
- <%= f.select(
35
- field.attribute,
36
- options_from_collection_for_select(
37
- field.selectable_options,
38
- :to_s,
39
- :to_s,
40
- field.data,
41
- ),
42
- include_blank: field.include_blank_option
43
- ) %>
44
- <% end %>
30
+ )
31
+ %>
45
32
  </div>
@@ -31,7 +31,7 @@ By default, it renders:
31
31
  <div class="app-container">
32
32
  <%= render "navigation" -%>
33
33
 
34
- <main class="main-content" role="main">
34
+ <main class="main-content">
35
35
  <%= render "flashes" -%>
36
36
  <%= yield %>
37
37
  </main>
@@ -5,9 +5,9 @@ ja:
5
5
  confirm: 本当によろしいですか?
6
6
  destroy: 削除
7
7
  edit: 編集
8
- edit_resource: 編集 %{name}
9
- show_resource: 参照 %{name}
10
- new_resource: 新規 %{name}
8
+ edit_resource: "%{name}を編集"
9
+ show_resource: "%{name}を参照"
10
+ new_resource: "%{name}を作成"
11
11
  back: 戻る
12
12
  controller:
13
13
  create:
@@ -22,9 +22,9 @@ ja:
22
22
  none: データがありません
23
23
  form:
24
24
  error: エラー
25
- errors: "%{pluralized_errors}のため%{resource_name}を保存できません。"
25
+ errors: "%{pluralized_errors}のため%{resource_name}を保存できませんでした。"
26
26
  navigation:
27
27
  back_to_app: アプリに戻る
28
28
  search:
29
29
  clear: 検索をクリアする
30
- label: サーチ %{resource}
30
+ label: "%{resource}を検索"
@@ -28,20 +28,30 @@ technically have access to see in the main app. For example, a user may
28
28
  have all public records in their scope, but you want to only show *their*
29
29
  records in the admin interface to reduce confusion.
30
30
 
31
- In this case, you can add an additional `resolve_admin` to your policy's
32
- scope and Administrate will use this instead of the `resolve` method.
31
+ In this case, you can add additional pundit `policy_namespace` in your controller
32
+ and Administrate will use the namespaced pundit policy instead.
33
33
 
34
34
  For example:
35
35
 
36
36
  ```ruby
37
- class PostPolicy < ApplicationPolicy
38
- class Scope < Scope
39
- def resolve
40
- scope.all
37
+ # app/controllers/admin/posts_controller.rb
38
+ module Admin
39
+ class PostsController < ApplicationController
40
+ include Administrate::Punditize
41
+
42
+ def policy_namespace
43
+ [:admin]
41
44
  end
45
+ end
46
+ end
42
47
 
43
- def resolve_admin
44
- scope.where(owner: user)
48
+ # app/policies/admin/post_policy.rb
49
+ module Admin
50
+ class PostPolicy < ApplicationPolicy
51
+ class Scope < Scope
52
+ def resolve
53
+ scope.where(owner: user)
54
+ end
45
55
  end
46
56
  end
47
57
  end
@@ -93,3 +93,17 @@ To set custom redirects after the actions `create`, `update` and `destroy` you c
93
93
  [namespace, requested_resource.some_other_resource]
94
94
  end
95
95
  ```
96
+
97
+ ## Creating Records
98
+
99
+ You can perform actions after creation by passing a `block` to `super` in the
100
+ `create` method. The block will only be called if the resource is successfully
101
+ created.
102
+
103
+ ```ruby
104
+ def create
105
+ super do |resource|
106
+ # do something with the newly created resource
107
+ end
108
+ end
109
+ ```
@@ -113,8 +113,11 @@ association `belongs_to :country`, from your model.
113
113
 
114
114
  **Field::HasMany**
115
115
 
116
- `:limit` - Set the number of resources to display in the show view. Default is
117
- `5`.
116
+ `:collection_attributes` - Set the columns to display in the show view.
117
+ Default is COLLECTION_ATTRIBUTES in dashboard.
118
+
119
+ `:limit` - The number of resources (paginated) to display in the show view. To disable pagination,
120
+ set this to `0` or `false`. Default is `5`.
118
121
 
119
122
  `:sort_by` - What to sort the association by in the show view.
120
123
 
@@ -128,6 +131,10 @@ association `belongs_to :country`, from your model.
128
131
 
129
132
  **Field::HasOne**
130
133
 
134
+ `:order` - Specifies the column used to order the records. It will apply both in
135
+ the table views and in the dropdown menu on the record forms.
136
+ You can set multiple columns as well with direction. E.g.: `"name, email DESC"`.
137
+
131
138
  `:searchable` - Specify if the attribute should be considered when searching.
132
139
  Default is `false`.
133
140
 
@@ -164,8 +171,7 @@ more results than expected. Default is `false`.
164
171
  and works by by passing a hash that includes the formatter (`formatter`) and
165
172
  the options for the formatter (`formatter_options`). Defaults to the locale's
166
173
  delimiter when `formatter_options` does not include a `delimiter`. See the
167
- example below. Note that currently only
168
- `ActiveSupport::NumberHelper.number_to_delimited` is supported.
174
+ example below. All helpers from `ActiveSupport::NumberHelper` are supported.
169
175
 
170
176
  For example, you might use the following to display U.S. currency:
171
177
 
@@ -218,25 +224,31 @@ objects to display as.
218
224
 
219
225
  **Field::Select**
220
226
 
221
- `:collection` - Specify the options shown on the select field. It accept either
222
- an array or an object responding to `:call`. Defaults to `[]`.
227
+ `:collection` - The options available to select. The format is the same as for Rails's own [`options_for_select`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-options_for_select).
223
228
 
224
- To customize option labels, pass an array of pairs where the first element is the value submitted with the form and the second element is the label shown to the user.
225
-
226
- For example:
229
+ If the given value responds to `call`, this will be called and the result used instead. The call will receive an instance of the field as argument. For example:
227
230
 
228
231
  ```ruby
229
- currency = Field::Select.with_options(
230
- collection: [ ['usd', 'Dollar'], ['eur', 'Euro'], ['yen', 'Yen'] ]
232
+ confirmation: Field::Select.with_options(
233
+ collection: ->(field) {
234
+ person = field.resource
235
+ {
236
+ "no, #{person.name}" => "opt0",
237
+ "yes, #{person.name}" => "opt1",
238
+ "absolutely, #{person.name}" => "opt2",
239
+ }
240
+ },
231
241
  )
232
-
233
242
  ```
234
243
 
244
+ Administrate will detect if the attribute is an `ActiveRecord::Enum` and extract the available options. Note that if a `collection` is provided it will take precedence.
245
+
246
+ If no collection is provided and no enum can be detected, the list of options will be empty.
247
+
235
248
  `:searchable` - Specify if the attribute should be considered when searching.
236
249
  Default is `true`.
237
250
 
238
- `:include_blank` - Specifies if the select element to be rendered should include
239
- blank option. Default is `false`.
251
+ `:include_blank` - Similar to [the option of the same name accepted by Rails helpers](https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html). If provided, a "blank" option will be added first to the list of options, with the value of `include_blank` as label.
240
252
 
241
253
  **Field::String**
242
254
 
@@ -373,3 +385,54 @@ FORM_ATTRIBUTES_EDIT = [
373
385
  ```
374
386
 
375
387
  Or for custom action with constant name `"FORM_ATTRIBUTES_#{action.upcase}"`
388
+
389
+ ### Form Fields' Hints
390
+
391
+ You can show a brief text element below an input field by setting the
392
+ corresponding translation key using the path:
393
+
394
+ `administrate.field_hints.#{model_name}.#{field_name}`
395
+
396
+ For example, with a Customer dashboard with an email field you can add a
397
+ string value that will be used as text hint:
398
+
399
+ ```yml
400
+ en:
401
+ administrate:
402
+ field_hints:
403
+ customer:
404
+ email: field_hint
405
+ ```
406
+
407
+ ## Grouped Attributes
408
+
409
+ You may have models with a large number of fields and therefore you might want to group them in a meaningul way:
410
+
411
+ ```ruby
412
+ class UserDashboard < Administrate::BaseDashboard
413
+ SHOW_PAGE_ATTRIBUTES = {
414
+ "" => [:username],
415
+ "Personal Information" => [:first_name, :last_name, :email],
416
+ "Address" => [:address_line_one, :address_line_two, :address_city, :address_state, :address_country]
417
+ }
418
+
419
+ FORM_ATTRIBUTES = {
420
+ "" => [:username, :password, :password_confirmation],
421
+ "personal_information" => [:first_name, :last_name, :email],
422
+ "address" => [:address_line_one, :address_line_two, :address_city, :address_state, :address_country]
423
+ }
424
+ end
425
+ ```
426
+
427
+ You can optionally translate your group labels:
428
+
429
+ ```yaml
430
+ en:
431
+ helpers:
432
+ label:
433
+ user:
434
+ address: Address
435
+ personal_information: Personal Information
436
+ ```
437
+
438
+ If not defined (see `SHOW_PAGE_ATTRIBUTES` above), Administrate will default to the given strings.
@@ -3,7 +3,7 @@ title: Getting Started
3
3
  ---
4
4
 
5
5
  Administrate is released as a Ruby gem, and can be installed on Rails
6
- applications version 5.0 or greater. We support Ruby 2.7 and up.
6
+ applications version 6.0 or greater. We support Ruby 3.0 and up.
7
7
 
8
8
  First, add the following to your Gemfile:
9
9
 
@@ -135,7 +135,7 @@ records.
135
135
 
136
136
  ## A working example
137
137
 
138
- The [Administrate demo app](/admin)
138
+ The [Administrate demo app](https://administrate-demo.herokuapp.com/admin)
139
139
  includes an example of custom search in the "Log Entries" dashboard.
140
140
  In this app, each `LogEntry` instance has a polymorphic `belongs_to`
141
141
  association to a `:logeable`. Logeables are other models for which logs can be