administrate 0.13.0 → 0.14.0

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/administrate/components/{has_many_form.js → associative.js} +1 -0
  3. data/app/assets/stylesheets/administrate/base/_tables.scss +3 -0
  4. data/app/assets/stylesheets/administrate/components/_flashes.scss +0 -8
  5. data/app/assets/stylesheets/administrate/library/_variables.scss +10 -8
  6. data/app/controllers/administrate/application_controller.rb +25 -4
  7. data/app/helpers/administrate/application_helper.rb +23 -11
  8. data/app/views/administrate/application/_collection.html.erb +5 -3
  9. data/app/views/administrate/application/_form.html.erb +1 -1
  10. data/app/views/administrate/application/_navigation.html.erb +4 -4
  11. data/app/views/fields/select/_form.html.erb +21 -9
  12. data/app/views/fields/time/_form.html.erb +3 -2
  13. data/config/locales/administrate.ar.yml +2 -0
  14. data/config/locales/administrate.bs.yml +2 -0
  15. data/config/locales/administrate.ca.yml +2 -0
  16. data/config/locales/administrate.da.yml +2 -0
  17. data/config/locales/administrate.de.yml +2 -0
  18. data/config/locales/administrate.en.yml +2 -0
  19. data/config/locales/administrate.es.yml +2 -0
  20. data/config/locales/administrate.fr.yml +2 -0
  21. data/config/locales/administrate.id.yml +2 -0
  22. data/config/locales/administrate.it.yml +2 -0
  23. data/config/locales/administrate.ja.yml +2 -0
  24. data/config/locales/administrate.ko.yml +2 -0
  25. data/config/locales/administrate.nl.yml +3 -1
  26. data/config/locales/administrate.pl.yml +2 -0
  27. data/config/locales/administrate.pt-BR.yml +2 -0
  28. data/config/locales/administrate.pt.yml +2 -0
  29. data/config/locales/administrate.ru.yml +2 -0
  30. data/config/locales/{administrate.al.yml → administrate.sq.yml} +3 -1
  31. data/config/locales/administrate.sv.yml +2 -0
  32. data/config/locales/administrate.uk.yml +2 -0
  33. data/config/locales/administrate.vi.yml +2 -0
  34. data/config/locales/administrate.zh-CN.yml +2 -0
  35. data/config/locales/administrate.zh-TW.yml +2 -0
  36. data/docs/adding_controllers_without_related_model.md +36 -0
  37. data/docs/customizing_controller_actions.md +14 -0
  38. data/docs/customizing_dashboards.md +6 -5
  39. data/docs/customizing_page_views.md +15 -3
  40. data/lib/administrate/base_dashboard.rb +13 -8
  41. data/lib/administrate/custom_dashboard.rb +15 -0
  42. data/lib/administrate/field/associative.rb +1 -1
  43. data/lib/administrate/field/base.rb +4 -0
  44. data/lib/administrate/field/deferred.rb +15 -0
  45. data/lib/administrate/field/polymorphic.rb +1 -1
  46. data/lib/administrate/field/select.rb +6 -1
  47. data/lib/administrate/namespace.rb +4 -0
  48. data/lib/administrate/page/base.rb +1 -1
  49. data/lib/administrate/page/form.rb +1 -1
  50. data/lib/administrate/search.rb +22 -18
  51. data/lib/administrate/version.rb +1 -1
  52. data/lib/generators/administrate/dashboard/dashboard_generator.rb +14 -3
  53. metadata +9 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a347d47197b8258a2233e4bed126e17faf34cedf8b87ca21ad61283c7f5b8dcf
4
- data.tar.gz: 2a63e64339323c68e2daae8df8c48a50c2a85aa25cfded6a14899a275e523573
3
+ metadata.gz: e8120deb0ddc3a88302230382298ce39c25d60e4effca6336787016c46024d91
4
+ data.tar.gz: ad6262c38fca6d8be885d8687db7b02444da05c751c8dcb25a71d7a292d43f05
5
5
  SHA512:
6
- metadata.gz: e8e751c67ebafc99997f82646120be75a79f021e6a78ea207b1a393e38438abd2c9fe00596a9772514d86e9b079984b6dd110eaef6541517dbf369b27acce590
7
- data.tar.gz: ad12aac58cee58d0eb620c0b60627f07bfddaa7853e6c518b0a2cbe4d219c61d64a41b761d7a958bf8b65c4003ecca74b572245ae1db54e2616aafe4420e494c
6
+ metadata.gz: e6bfde68398c548cfe04665c21c5e44fd8f659c404ddfcb141c5b358396cb6d0ca1c087b459a322553ff9dfeb195f033d817829d7a84bc80024004f1390faf5c
7
+ data.tar.gz: 794416e6cc50fadef9674c2fcb5316be67e7523d19c31431dfe3f80165a5355f3ce12019aad78681b6f5ede71797f3b3bab9b911236e8f3231bd0fcdacd15bc2
@@ -1,3 +1,4 @@
1
1
  $(function() {
2
+ $('.field-unit--belongs-to select').selectize({});
2
3
  $(".field-unit--has-many select").selectize({});
3
4
  });
@@ -21,6 +21,9 @@ tr {
21
21
  tbody tr {
22
22
  &:hover {
23
23
  background-color: $base-background-color;
24
+ }
25
+
26
+ [role=link] {
24
27
  cursor: pointer;
25
28
  }
26
29
 
@@ -1,11 +1,3 @@
1
- $base-spacing: 1.5em !default;
2
- $flashes: (
3
- "alert": #fff6bf,
4
- "error": #fbe3e4,
5
- "notice": #e5edf8,
6
- "success": #e6efc2,
7
- ) !default;
8
-
9
1
  @each $flash-type, $color in $flashes {
10
2
  .flash-#{$flash-type} {
11
3
  background-color: $color;
@@ -22,8 +22,10 @@ $black: #000 !default;
22
22
 
23
23
  $blue: #1976d2 !default;
24
24
  $red: #d32f2f !default;
25
- $light-yellow: #f0cd66 !default;
26
- $light-green: #4ab471 !default;
25
+ $light-yellow: #fff6bf !default;
26
+ $light-red: #fbe3e4 !default;
27
+ $light-green: #e6efc2 !default;
28
+ $light-blue: #e5edf8 !default;
27
29
 
28
30
  $grey-0: #f6f7f7 !default;
29
31
  $grey-1: #dfe0e1 !default;
@@ -47,12 +49,12 @@ $focus-outline: $focus-outline-width solid $focus-outline-color;
47
49
  $focus-outline-offset: 1px;
48
50
 
49
51
  // Flash Colors
50
- $flash-colors: (
51
- alert: $light-yellow,
52
- error: $red,
53
- notice: mix($white, $blue, 50%),
54
- success: $light-green
55
- );
52
+ $flashes: (
53
+ "alert": $light-yellow,
54
+ "error": $light-red,
55
+ "notice": $light-blue,
56
+ "success": $light-green
57
+ ) !default;
56
58
 
57
59
  // Border
58
60
  $base-border-color: $grey-1 !default;
@@ -27,7 +27,7 @@ module Administrate
27
27
  end
28
28
 
29
29
  def new
30
- resource = resource_class.new
30
+ resource = new_resource
31
31
  authorize_resource(resource)
32
32
  render locals: {
33
33
  page: Administrate::Page::Form.new(dashboard, resource),
@@ -101,12 +101,31 @@ module Administrate
101
101
  end
102
102
 
103
103
  def order
104
- @order ||= Administrate::Order.new(
105
- params.fetch(resource_name, {}).fetch(:order, nil),
106
- params.fetch(resource_name, {}).fetch(:direction, nil),
104
+ @order ||= Administrate::Order.new(sorting_attribute, sorting_direction)
105
+ end
106
+
107
+ def sorting_attribute
108
+ params.fetch(resource_name, {}).fetch(
109
+ :order,
110
+ default_sorting_attribute,
107
111
  )
108
112
  end
109
113
 
114
+ def default_sorting_attribute
115
+ nil
116
+ end
117
+
118
+ def sorting_direction
119
+ params.fetch(resource_name, {}).fetch(
120
+ :direction,
121
+ default_sorting_direction,
122
+ )
123
+ end
124
+
125
+ def default_sorting_direction
126
+ nil
127
+ end
128
+
110
129
  def dashboard
111
130
  @dashboard ||= dashboard_class.new
112
131
  end
@@ -144,6 +163,8 @@ module Administrate
144
163
  else
145
164
  raise "Unrecognised param data: #{data.inspect}"
146
165
  end
166
+ elsif data.is_a?(ActionController::Parameters)
167
+ data.transform_values { |v| read_param_value(v) }
147
168
  else
148
169
  data
149
170
  end
@@ -11,7 +11,7 @@ module Administrate
11
11
  end
12
12
 
13
13
  def render_field(field, locals = {})
14
- locals.merge!(field: field)
14
+ locals[:field] = field
15
15
  render locals: locals, partial: field.to_partial_path
16
16
  end
17
17
 
@@ -34,17 +34,20 @@ module Administrate
34
34
  resource_class.validators_on(field_name)
35
35
  end
36
36
 
37
- def class_from_resource(resource_name)
38
- resource_name.to_s.classify.constantize
37
+ def dashboard_from_resource(resource_name)
38
+ "#{resource_name.to_s.singularize}_dashboard".classify.constantize
39
+ end
40
+
41
+ def model_from_resource(resource_name)
42
+ dashboard = dashboard_from_resource(resource_name)
43
+ dashboard.try(:model) || resource_name.to_sym
39
44
  end
40
45
 
41
46
  def display_resource_name(resource_name)
42
- class_from_resource(resource_name).
43
- model_name.
44
- human(
45
- count: PLURAL_MANY_COUNT,
46
- default: resource_name.to_s.pluralize.titleize,
47
- )
47
+ dashboard_from_resource(resource_name).resource_name(
48
+ count: PLURAL_MANY_COUNT,
49
+ default: default_resource_name(resource_name),
50
+ )
48
51
  end
49
52
 
50
53
  def sort_order(order)
@@ -55,8 +58,11 @@ module Administrate
55
58
  end
56
59
  end
57
60
 
58
- def resource_index_route_key(resource_name)
59
- ActiveModel::Naming.route_key(class_from_resource(resource_name))
61
+ def resource_index_route(resource_name)
62
+ url_for(
63
+ action: "index",
64
+ controller: "/#{namespace}/#{resource_name}",
65
+ )
60
66
  end
61
67
 
62
68
  def sanitized_order_params(page, current_field_name)
@@ -72,5 +78,11 @@ module Administrate
72
78
  :per_page, resource_name => %i[order direction]
73
79
  )
74
80
  end
81
+
82
+ private
83
+
84
+ def default_resource_name(resource_name)
85
+ resource_name.to_s.pluralize.gsub("/", "_").titleize
86
+ end
75
87
  end
76
88
  end
@@ -55,19 +55,21 @@ to display a collection of resources in an HTML table.
55
55
  <tbody>
56
56
  <% resources.each do |resource| %>
57
57
  <tr class="js-table-row"
58
- tabindex="0"
59
- <% if valid_action? :show, collection_presenter.resource_name %>
60
- <%= %(role=link data-url=#{polymorphic_path([namespace, resource])}) %>
58
+ <% if show_action? :show, resource %>
59
+ <%= %(tabindex=0 role=link data-url=#{polymorphic_path([namespace, resource])}) %>
61
60
  <% end %>
62
61
  >
63
62
  <% collection_presenter.attributes_for(resource).each do |attribute| %>
64
63
  <td class="cell-data cell-data--<%= attribute.html_class %>">
65
64
  <% if show_action? :show, resource -%>
66
65
  <a href="<%= polymorphic_path([namespace, resource]) -%>"
66
+ tabindex="-1"
67
67
  class="action-show"
68
68
  >
69
69
  <%= render_field attribute %>
70
70
  </a>
71
+ <% else %>
72
+ <%= render_field attribute %>
71
73
  <% end -%>
72
74
  </td>
73
75
  <% end %>
@@ -21,7 +21,7 @@ and renders all form fields for a resource's editable attributes.
21
21
  <%= t(
22
22
  "administrate.form.errors",
23
23
  pluralized_errors: pluralize(page.resource.errors.count, t("administrate.form.error")),
24
- resource_name: display_resource_name(page.resource_name)
24
+ resource_name: display_resource_name(page.resource_name).singularize
25
25
  ) %>
26
26
  </h2>
27
27
 
@@ -8,13 +8,13 @@ as defined by the routes in the `admin/` namespace
8
8
  %>
9
9
 
10
10
  <nav class="navigation" role="navigation">
11
- <%= link_to "Back to app", root_url, class: "button button--alt" %>
11
+ <%= link_to(t("administrate.navigation.back_to_app"), root_url, class: "button button--alt") if defined?(root_url) %>
12
12
 
13
- <% Administrate::Namespace.new(namespace).resources.each do |resource| %>
13
+ <% Administrate::Namespace.new(namespace).resources_with_index_route.each do |resource| %>
14
14
  <%= link_to(
15
15
  display_resource_name(resource),
16
- [namespace, resource_index_route_key(resource)],
16
+ resource_index_route(resource),
17
17
  class: "navigation__link navigation__link--#{nav_link_state(resource)}"
18
- ) if valid_action? :index, resource %>
18
+ ) if valid_action?(:index, resource) && show_action?(:index, model_from_resource(resource)) %>
19
19
  <% end %>
20
20
  </nav>
@@ -19,13 +19,25 @@ 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
- <%= f.select(
23
- field.attribute,
24
- options_from_collection_for_select(
25
- field.selectable_options,
26
- :to_s,
27
- :to_s,
28
- field.data.presence,
29
- )
30
- ) %>
22
+ <% if field.selectable_options.first&.is_a?(Array) %>
23
+ <%= f.select(
24
+ field.attribute,
25
+ options_from_collection_for_select(
26
+ field.selectable_options,
27
+ :last,
28
+ :first,
29
+ field.data.presence,
30
+ )
31
+ ) %>
32
+ <% else %>
33
+ <%= f.select(
34
+ field.attribute,
35
+ options_from_collection_for_select(
36
+ field.selectable_options,
37
+ :to_s,
38
+ :to_s,
39
+ field.data.presence,
40
+ )
41
+ ) %>
42
+ <% end %>
31
43
  </div>
@@ -2,7 +2,7 @@
2
2
  # Time Form Partial
3
3
 
4
4
  This partial renders an input element for time attributes.
5
- By default, the input is a select field for the time attributes.
5
+ By default, the input is a text field that is augmented with [DateTimePicker].
6
6
 
7
7
  ## Local variables:
8
8
 
@@ -12,11 +12,12 @@ By default, the input is a select field for the time attributes.
12
12
  An instance of [Administrate::Field::Time][1].
13
13
  A wrapper around the tmie attributes pulled from the model.
14
14
 
15
+ [DateTimePicker]: https://github.com/Eonasdan/bootstrap-datetimepicker
15
16
  %>
16
17
 
17
18
  <div class="field-unit__label">
18
19
  <%= f.label field.attribute %>
19
20
  </div>
20
21
  <div class="field-unit__field">
21
- <%= f.text_field field.attribute, data: { type: 'time' } %>
22
+ <%= f.text_field field.attribute, data: { type: 'time' }, value: field.data&.strftime("%H:%M:%S") %>
22
23
  </div>
@@ -23,6 +23,8 @@ ar:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
26
+ navigation:
27
+ back_to_app: العودة إلى التطبيق
26
28
  search:
27
29
  clear: مسح البحث
28
30
  label: بحث %{resource}
@@ -22,6 +22,8 @@ bs:
22
22
  form:
23
23
  error: error
24
24
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
25
+ navigation:
26
+ back_to_app: Natrag na aplikaciju
25
27
  search:
26
28
  clear: Izbriši pretraživanje
27
29
  label: Pretraga %{resource}
@@ -23,6 +23,8 @@ ca:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} han impedit que %{resource_name} es guardés amb èxit:"
26
+ navigation:
27
+ back_to_app: Torna a l'aplicació
26
28
  search:
27
29
  clear: Esborrar la cerca
28
30
  label: Cerca %{resource}
@@ -23,6 +23,8 @@ da:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
26
+ navigation:
27
+ back_to_app: Tilbage til app
26
28
  search:
27
29
  clear: Ryd søgning
28
30
  label: Søg %{resource}
@@ -23,6 +23,8 @@ de:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} haben das Speichern dieses %{resource_name} verhindert:"
26
+ navigation:
27
+ back_to_app: Zurück zur App
26
28
  search:
27
29
  clear: Suche zurücksetzen
28
30
  label: "%{resource} durchsuchen"
@@ -23,6 +23,8 @@ en:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
26
+ navigation:
27
+ back_to_app: Back to app
26
28
  search:
27
29
  clear: Clear search
28
30
  label: Search %{resource}
@@ -23,6 +23,8 @@ es:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} no permitieron guardar este %{resource_name}:"
26
+ navigation:
27
+ back_to_app: Regresar a la aplicación
26
28
  search:
27
29
  clear: Borrar búsqueda
28
30
  label: Buscar %{resource}
@@ -23,6 +23,8 @@ fr:
23
23
  form:
24
24
  error: erreur
25
25
  errors: "%{pluralized_errors} ont empêchés %{resource_name} d'être sauvergardé :"
26
+ navigation:
27
+ back_to_app: Retour à l'application
26
28
  search:
27
29
  clear: Effacer la recherche
28
30
  label: Chercher %{resource}
@@ -23,6 +23,8 @@ id:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} membuat %{resource_name} tidak bisa tersimpan:"
26
+ navigation:
27
+ back_to_app: Kembali ke aplikasi
26
28
  search:
27
29
  clear: Kosongkan pencarian
28
30
  label: Cari %{resource}
@@ -23,6 +23,8 @@ it:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
26
+ navigation:
27
+ back_to_app: Torna all'app
26
28
  search:
27
29
  clear: Cancella ricerca
28
30
  label: Ricerca %{resource}
@@ -23,6 +23,8 @@ ja:
23
23
  form:
24
24
  error: エラー
25
25
  errors: "%{pluralized_errors}のため%{resource_name}を保存できません。"
26
+ navigation:
27
+ back_to_app: アプリに戻る
26
28
  search:
27
29
  clear: 検索をクリアする
28
30
  label: サーチ %{resource}
@@ -23,6 +23,8 @@ ko:
23
23
  form:
24
24
  error: 에러
25
25
  errors: "%{pluralized_errors}로 인하여 %{resource_name}을(를) 저장하는데 실패하였습니다."
26
+ navigation:
27
+ back_to_app: 앱으로 돌아 가기
26
28
  search:
27
29
  clear: 검색 초기화
28
30
  label: "%{resource} 검색"
@@ -23,6 +23,8 @@ nl:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
26
+ navigation:
27
+ back_to_app: Terug naar app
26
28
  search:
27
- clear: CDuidelijke zoek
29
+ clear: Wissen
28
30
  label: Zoeken %{resource}
@@ -23,6 +23,8 @@ pl:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
26
+ navigation:
27
+ back_to_app: Powrót do aplikacji
26
28
  search:
27
29
  clear: Wyczyść wyszukiwanie
28
30
  label: Szukanie %{resource}
@@ -24,6 +24,8 @@ pt-BR:
24
24
  form:
25
25
  error: error
26
26
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
27
+ navigation:
28
+ back_to_app: Voltar ao aplicativo
27
29
  search:
28
30
  clear: Limpar pesquisa
29
31
  label: Pesquisa %{resource}
@@ -24,6 +24,8 @@ pt:
24
24
  form:
25
25
  error: error
26
26
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
27
+ navigation:
28
+ back_to_app: Voltar ao aplicativo
27
29
  search:
28
30
  clear: Limpar pesquisa
29
31
  label: Pesquisa %{resource}
@@ -23,6 +23,8 @@ ru:
23
23
  form:
24
24
  error: Ошибка
25
25
  errors: "При сохранении %{resource_name} произошли ошибки:"
26
+ navigation:
27
+ back_to_app: Вернуться в приложение
26
28
  search:
27
29
  clear: Очистить поиск
28
30
  label: Поиск %{resource}
@@ -1,5 +1,5 @@
1
1
  ---
2
- al:
2
+ sq:
3
3
  administrate:
4
4
  actions:
5
5
  confirm: A jeni te sigurtë?
@@ -23,6 +23,8 @@ al:
23
23
  form:
24
24
  error: gabim
25
25
  errors: "%{pluralized_errors} nuk e lejoj %{resource_name} të ruhet:"
26
+ navigation:
27
+ back_to_app: Kthehu tek aplikacioni
26
28
  search:
27
29
  clear: Pastro kërkimin
28
30
  label: Kërko %{resource}
@@ -23,6 +23,8 @@ sv:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
26
+ navigation:
27
+ back_to_app: Tillbaka till appen
26
28
  search:
27
29
  clear: Rensa sökningen
28
30
  label: Sök %{resource}
@@ -23,6 +23,8 @@ uk:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
26
+ navigation:
27
+ back_to_app: Повернутися до програми
26
28
  search:
27
29
  clear: Очистити пошук
28
30
  label: пошук %{resource}
@@ -23,6 +23,8 @@ vi:
23
23
  form:
24
24
  error: error
25
25
  errors: "%{pluralized_errors} prohibited this %{resource_name} from being saved:"
26
+ navigation:
27
+ back_to_app: Quay lại ứng dụng
26
28
  search:
27
29
  clear: Tìm kiếm rõ ràng
28
30
  label: Tìm kiếm %{resource}
@@ -23,6 +23,8 @@ zh-CN:
23
23
  form:
24
24
  error: 错误
25
25
  errors: "%{resource_name} 保存前出现了 %{pluralized_errors} 个错误:"
26
+ navigation:
27
+ back_to_app: 返回应用
26
28
  search:
27
29
  clear: 清除搜索
28
30
  label: 搜索 %{resource}
@@ -23,6 +23,8 @@ zh-TW:
23
23
  form:
24
24
  error: 錯誤
25
25
  errors: "%{pluralized_errors} 導致此 %{resource_name} 不能被儲存:"
26
+ navigation:
27
+ back_to_app: 返回应用
26
28
  search:
27
29
  clear: 清除搜尋
28
30
  label: 搜尋 %{resource}
@@ -0,0 +1,36 @@
1
+ # Adding Controllers without a related Model
2
+
3
+ Sometimes you may want to add a custom controller that has no resource
4
+ related to it (for example for a statistics page).
5
+
6
+ To do that, you must define an `index` route, as only controllers with index
7
+ routes are displayed in the sidebar and then add a custom dashboard:
8
+
9
+ ```erb
10
+ # app/views/admin/stats/index.html.erb
11
+
12
+ <div style="padding: 20px">
13
+ <h1>Stats</h1>
14
+ <br>
15
+ <p><b>Total Customers:</b> <%= @stats[:customer_count] %></h1>
16
+ <br>
17
+ <p><b>Total Orders:</b> <%= @stats[:order_count] %></h1>
18
+ </div>
19
+ ```
20
+
21
+ ```ruby
22
+ # app/dashboards/stat_dashboard.rb
23
+ require "administrate/custom_dashboard"
24
+
25
+ class StatDashboard < Administrate::CustomDashboard
26
+ resource "Stats" # used by administrate in the views
27
+ end
28
+ ```
29
+
30
+ ```ruby
31
+ # config/routes.rb
32
+ namespace :admin do
33
+ # ...
34
+ resources :stats, only: [:index]
35
+ end
36
+ ```
@@ -56,3 +56,17 @@ end
56
56
  ```
57
57
 
58
58
  Action is one of `new`, `edit`, `show`, `destroy`.
59
+
60
+ ## Customizing Default Sorting
61
+
62
+ To set the default sorting on the index action you could override `default_sorting_attribute` or `default_sorting_direction` in your dashboard controller like this:
63
+
64
+ ```ruby
65
+ def default_sorting_attribute
66
+ :age
67
+ end
68
+
69
+ def default_sorting_direction
70
+ :desc
71
+ end
72
+ ```
@@ -93,7 +93,7 @@ Defaults to `:#{attribute}.to_s.singularize.camelcase`.
93
93
  `:searchable` - Specify if the attribute should be considered when searching.
94
94
  Default is `false`.
95
95
 
96
- `searchable_field` - Specify which column to use on the search, only applies
96
+ `searchable_fields` - Specify which columns to use on the search, only applies
97
97
  if `searchable` is `true`
98
98
 
99
99
  For example:
@@ -101,7 +101,7 @@ For example:
101
101
  ```ruby
102
102
  country: Field::BelongsTo.with_options(
103
103
  searchable: true,
104
- searchable_field: 'name',
104
+ searchable_fields: ['name'],
105
105
  )
106
106
  ```
107
107
 
@@ -132,7 +132,7 @@ Defaults to `:#{attribute}.to_s.singularize.camelcase`.
132
132
  `:searchable` - Specify if the attribute should be considered when searching.
133
133
  Default is `false`.
134
134
 
135
- `searchable_field` - Specify which column to use on the search, only applies if
135
+ `searchable_fields` - Specify which columns to use on the search, only applies if
136
136
  `searchable` is `true`
137
137
 
138
138
  For example:
@@ -140,7 +140,7 @@ For example:
140
140
  ```ruby
141
141
  cities: Field::HasMany.with_options(
142
142
  searchable: true,
143
- searchable_field: 'name',
143
+ searchable_fields: ['name'],
144
144
  )
145
145
  ```
146
146
 
@@ -200,7 +200,8 @@ objects to display as.
200
200
 
201
201
  **Field::Select**
202
202
 
203
- `:collection` - Specify the array or range to select from. Defaults to `[]`.
203
+ `:collection` - Specify the options shown on the select field. It accept either
204
+ an array or an object responding to `:call`. Defaults to `[]`.
204
205
 
205
206
  `:searchable` - Specify if the attribute should be considered when searching.
206
207
  Default is `true`.
@@ -2,12 +2,24 @@
2
2
  title: Customizing page views
3
3
  ---
4
4
 
5
- In order to change the appearance of any page,
6
- you can write custom Rails views.
5
+ You can provide replacements for any of Administrate's templates.
6
+ This way you can change the appearance of any page or element of
7
+ the interface.
8
+
9
+ In general, you can override any of the views under Administrate's
10
+ [/app/views][1].
11
+ For example, say that you want to customize the template used for flash
12
+ messages. You can provide your own as
13
+ `/app/views/administrate/application/_flashes.html.erb`, and it will replace
14
+ Administrate's own.
15
+
16
+ Figuring out which views are available and where can be repetitive. You can
17
+ spare yourself some effort by using the built-in view generators.
18
+
19
+ [1]: https://github.com/thoughtbot/administrate/tree/master/app/views
7
20
 
8
21
  ## Customizing for all resources
9
22
 
10
- The easiest way to get started is by using the built-in generators.
11
23
  In order to change the appearance of views for all resource types,
12
24
  call the generators with no arguments.
13
25
 
@@ -18,6 +18,18 @@ module Administrate
18
18
  class BaseDashboard
19
19
  include Administrate
20
20
 
21
+ DASHBOARD_SUFFIX = "Dashboard".freeze
22
+
23
+ class << self
24
+ def model
25
+ to_s.chomp(DASHBOARD_SUFFIX).classify.constantize
26
+ end
27
+
28
+ def resource_name(opts)
29
+ model.model_name.human(opts)
30
+ end
31
+ end
32
+
21
33
  def attribute_types
22
34
  self.class::ATTRIBUTE_TYPES
23
35
  end
@@ -74,18 +86,11 @@ module Administrate
74
86
  "Attribute #{attr} could not be found in #{self.class}::ATTRIBUTE_TYPES"
75
87
  end
76
88
 
77
- def association_classes
78
- @association_classes ||=
79
- ObjectSpace.each_object(Class).
80
- select { |klass| klass < Administrate::Field::Associative }
81
- end
82
-
83
89
  def attribute_includes(attributes)
84
90
  attributes.map do |key|
85
91
  field = self.class::ATTRIBUTE_TYPES[key]
86
92
 
87
- next key if association_classes.include?(field)
88
- key if association_classes.include?(field.try(:deferred_class))
93
+ key if field.associative?
89
94
  end.compact
90
95
  end
91
96
  end
@@ -0,0 +1,15 @@
1
+ module Administrate
2
+ class CustomDashboard
3
+ include Administrate
4
+
5
+ class << self
6
+ def resource_name(_opts)
7
+ named_resource.pluralize.titleize
8
+ end
9
+
10
+ def resource(resource_name)
11
+ define_singleton_method(:named_resource) { resource_name }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -11,7 +11,7 @@ module Administrate
11
11
  associated_class_name.constantize
12
12
  end
13
13
 
14
- protected
14
+ private
15
15
 
16
16
  def associated_dashboard
17
17
  "#{associated_class_name}Dashboard".constantize.new
@@ -12,6 +12,10 @@ module Administrate
12
12
  field_type.dasherize
13
13
  end
14
14
 
15
+ def self.associative?
16
+ self < Associative
17
+ end
18
+
15
19
  def self.searchable?
16
20
  false
17
21
  end
@@ -21,14 +21,29 @@ module Administrate
21
21
  options == other.options
22
22
  end
23
23
 
24
+ def associative?
25
+ deferred_class.associative?
26
+ end
27
+
24
28
  def searchable?
25
29
  options.fetch(:searchable, deferred_class.searchable?)
26
30
  end
27
31
 
28
32
  def searchable_field
33
+ ActiveSupport::Deprecation.warn(
34
+ "searchable_field is deprecated, use searchable_fields instead",
35
+ )
29
36
  options.fetch(:searchable_field)
30
37
  end
31
38
 
39
+ def searchable_fields
40
+ if options.key?(:searchable_field)
41
+ [searchable_field]
42
+ else
43
+ options.fetch(:searchable_fields)
44
+ end
45
+ end
46
+
32
47
  def permitted_attribute(attr, _options = nil)
33
48
  options.fetch(:foreign_key,
34
49
  deferred_class.permitted_attribute(attr, options))
@@ -23,7 +23,7 @@ module Administrate
23
23
  data ? data.to_global_id : nil
24
24
  end
25
25
 
26
- protected
26
+ private
27
27
 
28
28
  def associated_dashboard(klass = data.class)
29
29
  "#{klass.name}Dashboard".constantize.new
@@ -14,7 +14,12 @@ module Administrate
14
14
  private
15
15
 
16
16
  def collection
17
- @collection ||= options.fetch(:collection, [])
17
+ values = options.fetch(:collection, [])
18
+ if values.respond_to? :call
19
+ return values.arity.positive? ? values.call(self) : values.call
20
+ end
21
+
22
+ @collection ||= values
18
23
  end
19
24
  end
20
25
  end
@@ -18,6 +18,10 @@ module Administrate
18
18
  end
19
19
  end
20
20
 
21
+ def resources_with_index_route
22
+ routes.select { |_resource, route| route == "index" }.map(&:first).uniq
23
+ end
24
+
21
25
  private
22
26
 
23
27
  attr_reader :namespace
@@ -23,7 +23,7 @@ module Administrate
23
23
  dashboard.try(:item_includes) || []
24
24
  end
25
25
 
26
- protected
26
+ private
27
27
 
28
28
  def attribute_field(dashboard, resource, attribute_name, page)
29
29
  value = get_attribute_value(resource, attribute_name)
@@ -20,7 +20,7 @@ module Administrate
20
20
  dashboard.display_resource(resource)
21
21
  end
22
22
 
23
- protected
23
+ private
24
24
 
25
25
  attr_reader :dashboard
26
26
  end
@@ -81,14 +81,24 @@ module Administrate
81
81
  def query_template
82
82
  search_attributes.map do |attr|
83
83
  table_name = query_table_name(attr)
84
- attr_name = column_to_query(attr)
85
-
86
- "LOWER(CAST(#{table_name}.#{attr_name} AS CHAR(256))) LIKE ?"
84
+ searchable_fields(attr).map do |field|
85
+ attr_name = column_to_query(field)
86
+ "LOWER(CAST(#{table_name}.#{attr_name} AS CHAR(256))) LIKE ?"
87
+ end.join(" OR ")
87
88
  end.join(" OR ")
88
89
  end
89
90
 
91
+ def searchable_fields(attr)
92
+ return [attr] unless association_search?(attr)
93
+
94
+ attribute_types[attr].searchable_fields
95
+ end
96
+
90
97
  def query_values
91
- ["%#{term.mb_chars.downcase}%"] * search_attributes.count
98
+ fields_count = search_attributes.sum do |attr|
99
+ searchable_fields(attr).count
100
+ end
101
+ ["%#{term.mb_chars.downcase}%"] * fields_count
92
102
  end
93
103
 
94
104
  def search_attributes
@@ -117,7 +127,12 @@ module Administrate
117
127
 
118
128
  def query_table_name(attr)
119
129
  if association_search?(attr)
120
- ActiveRecord::Base.connection.quote_table_name(attr.to_s.pluralize)
130
+ provided_class_name = attribute_types[attr].options[:class_name]
131
+ if provided_class_name
132
+ provided_class_name.constantize.table_name
133
+ else
134
+ ActiveRecord::Base.connection.quote_table_name(attr.to_s.pluralize)
135
+ end
121
136
  else
122
137
  ActiveRecord::Base.connection.
123
138
  quote_table_name(@scoped_resource.table_name)
@@ -125,12 +140,7 @@ module Administrate
125
140
  end
126
141
 
127
142
  def column_to_query(attr)
128
- if association_search?(attr)
129
- ActiveRecord::Base.connection.
130
- quote_column_name(attribute_types[attr].searchable_field)
131
- else
132
- ActiveRecord::Base.connection.quote_column_name(attr)
133
- end
143
+ ActiveRecord::Base.connection.quote_column_name(attr)
134
144
  end
135
145
 
136
146
  def tables_to_join
@@ -140,13 +150,7 @@ module Administrate
140
150
  end
141
151
 
142
152
  def association_search?(attribute)
143
- return unless attribute_types[attribute].respond_to?(:deferred_class)
144
-
145
- [
146
- Administrate::Field::BelongsTo,
147
- Administrate::Field::HasMany,
148
- Administrate::Field::HasOne,
149
- ].include?(attribute_types[attribute].deferred_class)
153
+ attribute_types[attribute].associative?
150
154
  end
151
155
 
152
156
  def term
@@ -1,3 +1,3 @@
1
1
  module Administrate
2
- VERSION = "0.13.0".freeze
2
+ VERSION = "0.14.0".freeze
3
3
  end
@@ -7,7 +7,7 @@ module Administrate
7
7
  boolean: "Field::Boolean",
8
8
  date: "Field::Date",
9
9
  datetime: "Field::DateTime",
10
- enum: "Field::String",
10
+ enum: "Field::Select",
11
11
  float: "Field::Number",
12
12
  integer: "Field::Number",
13
13
  time: "Field::Time",
@@ -16,7 +16,9 @@ module Administrate
16
16
  }
17
17
 
18
18
  ATTRIBUTE_OPTIONS_MAPPING = {
19
- enum: { searchable: false },
19
+ # procs must be defined in one line!
20
+ enum: { searchable: false,
21
+ collection: ->(field) { field.resource.class.send(field.attribute.to_s.pluralize).keys } },
20
22
  float: { decimals: 2 },
21
23
  }
22
24
 
@@ -136,7 +138,16 @@ module Administrate
136
138
  end
137
139
 
138
140
  def inspect_hash_as_ruby(hash)
139
- hash.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
141
+ hash.map do |key, value|
142
+ v_str = value.respond_to?(:call) ? proc_string(value) : value.inspect
143
+ "#{key}: #{v_str}"
144
+ end.join(", ")
145
+ end
146
+
147
+ def proc_string(value)
148
+ source = value.source_location
149
+ proc_string = IO.readlines(source.first)[source.second - 1]
150
+ proc_string[/->[^}]*} | (lambda|proc).*end/x]
140
151
  end
141
152
  end
142
153
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: administrate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Charlton
8
8
  - Grayson Wright
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-03-13 00:00:00.000000000 Z
12
+ date: 2020-07-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -187,8 +187,8 @@ extra_rdoc_files: []
187
187
  files:
188
188
  - Rakefile
189
189
  - app/assets/javascripts/administrate/application.js
190
+ - app/assets/javascripts/administrate/components/associative.js
190
191
  - app/assets/javascripts/administrate/components/date_time_picker.js
191
- - app/assets/javascripts/administrate/components/has_many_form.js
192
192
  - app/assets/javascripts/administrate/components/table.js
193
193
  - app/assets/stylesheets/administrate/application.scss
194
194
  - app/assets/stylesheets/administrate/base/_forms.scss
@@ -275,7 +275,6 @@ files:
275
275
  - app/views/fields/url/_show.html.erb
276
276
  - app/views/layouts/administrate/application.html.erb
277
277
  - config/i18n-tasks.yml
278
- - config/locales/administrate.al.yml
279
278
  - config/locales/administrate.ar.yml
280
279
  - config/locales/administrate.bs.yml
281
280
  - config/locales/administrate.ca.yml
@@ -293,6 +292,7 @@ files:
293
292
  - config/locales/administrate.pt-BR.yml
294
293
  - config/locales/administrate.pt.yml
295
294
  - config/locales/administrate.ru.yml
295
+ - config/locales/administrate.sq.yml
296
296
  - config/locales/administrate.sv.yml
297
297
  - config/locales/administrate.uk.yml
298
298
  - config/locales/administrate.vi.yml
@@ -300,6 +300,7 @@ files:
300
300
  - config/locales/administrate.zh-TW.yml
301
301
  - config/routes.rb
302
302
  - config/unicorn.rb
303
+ - docs/adding_controllers_without_related_model.md
303
304
  - docs/adding_custom_field_types.md
304
305
  - docs/authentication.md
305
306
  - docs/authorization.md
@@ -312,6 +313,7 @@ files:
312
313
  - docs/rails_api.md
313
314
  - lib/administrate.rb
314
315
  - lib/administrate/base_dashboard.rb
316
+ - lib/administrate/custom_dashboard.rb
315
317
  - lib/administrate/engine.rb
316
318
  - lib/administrate/field/associative.rb
317
319
  - lib/administrate/field/base.rb
@@ -374,7 +376,7 @@ homepage: https://administrate-prototype.herokuapp.com/
374
376
  licenses:
375
377
  - MIT
376
378
  metadata: {}
377
- post_install_message:
379
+ post_install_message:
378
380
  rdoc_options: []
379
381
  require_paths:
380
382
  - lib
@@ -390,7 +392,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
390
392
  version: '0'
391
393
  requirements: []
392
394
  rubygems_version: 3.0.3
393
- signing_key:
395
+ signing_key:
394
396
  specification_version: 4
395
397
  summary: A Rails engine for creating super-flexible admin dashboards
396
398
  test_files: []