administrate 0.18.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/administrate/components/select.js +3 -0
  3. data/app/controllers/administrate/application_controller.rb +6 -4
  4. data/app/controllers/concerns/administrate/punditize.rb +38 -12
  5. data/app/views/administrate/application/_collection.html.erb +2 -3
  6. data/app/views/administrate/application/_index_header.html.erb +1 -1
  7. data/app/views/administrate/application/_navigation.html.erb +1 -1
  8. data/app/views/administrate/application/_pagination.html.erb +1 -1
  9. data/app/views/administrate/application/edit.html.erb +1 -1
  10. data/app/views/administrate/application/new.html.erb +1 -1
  11. data/app/views/administrate/application/show.html.erb +1 -1
  12. data/app/views/fields/has_many/_show.html.erb +2 -1
  13. data/app/views/fields/select/_form.html.erb +5 -18
  14. data/app/views/layouts/administrate/application.html.erb +1 -1
  15. data/config/locales/administrate.ja.yml +5 -5
  16. data/docs/authorization.md +18 -8
  17. data/docs/customizing_dashboards.md +22 -12
  18. data/docs/getting_started.md +1 -1
  19. data/docs/guides/customising_search.md +1 -1
  20. data/lib/administrate/base_dashboard.rb +9 -2
  21. data/lib/administrate/field/associative.rb +11 -1
  22. data/lib/administrate/field/belongs_to.rb +5 -2
  23. data/lib/administrate/field/has_many.rb +15 -5
  24. data/lib/administrate/field/polymorphic.rb +2 -1
  25. data/lib/administrate/field/select.rb +19 -9
  26. data/lib/administrate/not_authorized_error.rb +3 -1
  27. data/lib/administrate/order.rb +43 -18
  28. data/lib/administrate/page/form.rb +0 -7
  29. data/lib/administrate/resource_resolver.rb +2 -1
  30. data/lib/administrate/search.rb +1 -1
  31. data/lib/administrate/version.rb +1 -1
  32. data/lib/administrate/view_generator.rb +6 -1
  33. data/lib/generators/administrate/dashboard/dashboard_generator.rb +6 -1
  34. data/lib/generators/administrate/dashboard/templates/controller.rb.erb +2 -2
  35. data/lib/generators/administrate/install/install_generator.rb +6 -1
  36. data/lib/generators/administrate/routes/routes_generator.rb +11 -2
  37. data/lib/generators/administrate/test_record.rb +21 -0
  38. metadata +5 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6711a800e61649dffc4e96f9ddfaf3af21d364a1cfc659959dc5496357af262
4
- data.tar.gz: 3dc0046165d843a60359343d756b584cb7d5a8a505c5ba4c2858749b4aceba28
3
+ metadata.gz: a94bb51af4fa730f84d042f402122cc3073894e67494309dab4621b5f39b36fa
4
+ data.tar.gz: 57e7d78f316c635be65735e5c4220bd4e8d777c29c1d92741ab2520998080cde
5
5
  SHA512:
6
- metadata.gz: 88099b1defc211784c017a82dc2a4f90d91599f664a768acb652dd5a9ac3358b00180bc1f7da7b072e26cb1412b967fb7c244096005ee782fb1719340a832f3b
7
- data.tar.gz: 2d01b4c1131ed1a7c2d9f70756c5d9b0b442abf846425c486ae0d9b5ee6d65c9d3e0e3f45b1d9864749c6901cc29cc90f3261c229ba8ba96e26ee552f92e9d88
6
+ metadata.gz: 4d7b0a4b21404d16b48a5545e57202b73b3d782e23a5b26b5ed9e52ab3a66069717e23b1e099727eba9ff99315ba757d8ad0b739895a56775fbb5f3f8279df29
7
+ data.tar.gz: 07cc800c0c7cca79231ac047d10120ef63ec1c7d95aa5dccac9c7b1b5a99cafaccc2c31165c7350920721e622063190b0f804fb05d1e436c518511639802d4bd
@@ -0,0 +1,3 @@
1
+ $(function() {
2
+ $('.field-unit--select select').selectize({});
3
+ });
@@ -40,7 +40,7 @@ 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
@@ -201,7 +201,7 @@ module Administrate
201
201
 
202
202
  def resource_params
203
203
  params.require(resource_class.model_name.param_key).
204
- permit(dashboard.permitted_attributes).
204
+ permit(dashboard.permitted_attributes(action_name)).
205
205
  transform_values { |v| read_param_value(v) }
206
206
  end
207
207
 
@@ -214,6 +214,8 @@ module Administrate
214
214
  end
215
215
  elsif data.is_a?(ActionController::Parameters)
216
216
  data.transform_values { |v| read_param_value(v) }
217
+ elsif data.is_a?(String) && data.blank?
218
+ nil
217
219
  else
218
220
  data
219
221
  end
@@ -265,8 +267,8 @@ module Administrate
265
267
  end
266
268
  helper_method :show_action?
267
269
 
268
- def new_resource
269
- resource_class.new
270
+ def new_resource(params = {})
271
+ resource_class.new(params)
270
272
  end
271
273
  helper_method :new_resource
272
274
 
@@ -2,37 +2,63 @@ module Administrate
2
2
  module Punditize
3
3
  if Object.const_defined?("Pundit")
4
4
  extend ActiveSupport::Concern
5
- include Pundit::Authorization
5
+
6
+ if Pundit.const_defined?(:Authorization)
7
+ include Pundit::Authorization
8
+ else
9
+ include Pundit
10
+ end
6
11
 
7
12
  included do
8
13
  private
9
14
 
15
+ def policy_namespace
16
+ []
17
+ end
18
+
10
19
  def scoped_resource
11
- policy_scope_admin super
20
+ namespaced_scope = policy_namespace + [super]
21
+ policy_scope!(pundit_user, namespaced_scope)
12
22
  end
13
23
 
14
24
  def authorize_resource(resource)
15
- authorize resource
25
+ namespaced_resource = policy_namespace + [resource]
26
+ authorize namespaced_resource
16
27
  end
17
28
 
18
29
  def authorized_action?(resource, action)
19
- Pundit.policy!(pundit_user, resource).send("#{action}?".to_sym)
30
+ namespaced_resource = policy_namespace + [resource]
31
+ policy = Pundit.policy!(pundit_user, namespaced_resource)
32
+ policy.send("#{action}?".to_sym)
20
33
  end
21
34
  end
22
35
 
23
36
  private
24
37
 
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
38
+ def policy_scope!(user, scope)
39
+ policy_scope_class = Pundit::PolicyFinder.new(scope).scope!
40
+
41
+ begin
42
+ policy_scope = policy_scope_class.new(user, pundit_model(scope))
43
+ rescue ArgumentError
44
+ raise(Pundit::InvalidConstructorError,
45
+ "Invalid #<#{policy_scope_class}> constructor is called")
46
+ end
47
+
48
+ if policy_scope.respond_to? :resolve_admin
49
+ ActiveSupport::Deprecation.warn(
50
+ "Pundit policy scope `resolve_admin` method is deprecated. " +
51
+ "Please use a namespaced pundit policy instead.",
52
+ )
53
+ policy_scope.resolve_admin
32
54
  else
33
- ps.resolve
55
+ policy_scope.resolve
34
56
  end
35
57
  end
58
+
59
+ def pundit_model(record)
60
+ record.is_a?(Array) ? record.last : record
61
+ end
36
62
  end
37
63
  end
38
64
  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">
@@ -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>
@@ -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,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
@@ -113,8 +113,8 @@ 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
+ `:limit` - The number of resources (paginated) to display in the show view. To disable pagination,
117
+ set this to `0` or `false`. Default is `5`.
118
118
 
119
119
  `:sort_by` - What to sort the association by in the show view.
120
120
 
@@ -128,6 +128,10 @@ association `belongs_to :country`, from your model.
128
128
 
129
129
  **Field::HasOne**
130
130
 
131
+ `:order` - Specifies the column used to order the records. It will apply both in
132
+ the table views and in the dropdown menu on the record forms.
133
+ You can set multiple columns as well with direction. E.g.: `"name, email DESC"`.
134
+
131
135
  `:searchable` - Specify if the attribute should be considered when searching.
132
136
  Default is `false`.
133
137
 
@@ -218,25 +222,31 @@ objects to display as.
218
222
 
219
223
  **Field::Select**
220
224
 
221
- `:collection` - Specify the options shown on the select field. It accept either
222
- an array or an object responding to `:call`. Defaults to `[]`.
223
-
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
+ `: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).
225
226
 
226
- For example:
227
+ 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
228
 
228
229
  ```ruby
229
- currency = Field::Select.with_options(
230
- collection: [ ['usd', 'Dollar'], ['eur', 'Euro'], ['yen', 'Yen'] ]
230
+ confirmation: Field::Select.with_options(
231
+ collection: ->(field) {
232
+ person = field.resource
233
+ {
234
+ "no, #{person.name}" => "opt0",
235
+ "yes, #{person.name}" => "opt1",
236
+ "absolutely, #{person.name}" => "opt2",
237
+ }
238
+ },
231
239
  )
232
-
233
240
  ```
234
241
 
242
+ 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.
243
+
244
+ If no collection is provided and no enum can be detected, the list of options will be empty.
245
+
235
246
  `:searchable` - Specify if the attribute should be considered when searching.
236
247
  Default is `true`.
237
248
 
238
- `:include_blank` - Specifies if the select element to be rendered should include
239
- blank option. Default is `false`.
249
+ `: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
250
 
241
251
  **Field::String**
242
252
 
@@ -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 2.7 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
@@ -51,6 +51,12 @@ module Administrate
51
51
  end
52
52
 
53
53
  def form_attributes(action = nil)
54
+ action =
55
+ case action
56
+ when "update" then "edit"
57
+ when "create" then "new"
58
+ else action
59
+ end
54
60
  specific_form_attributes_for(action) || self.class::FORM_ATTRIBUTES
55
61
  end
56
62
 
@@ -62,11 +68,12 @@ module Administrate
62
68
  self.class.const_get(cname) if self.class.const_defined?(cname)
63
69
  end
64
70
 
65
- def permitted_attributes
66
- form_attributes.map do |attr|
71
+ def permitted_attributes(action = nil)
72
+ form_attributes(action).map do |attr|
67
73
  attribute_types[attr].permitted_attribute(
68
74
  attr,
69
75
  resource_class: self.class.model,
76
+ action: action,
70
77
  )
71
78
  end.uniq
72
79
  end
@@ -7,6 +7,10 @@ module Administrate
7
7
  reflection(resource_class, attr).foreign_key
8
8
  end
9
9
 
10
+ def self.association_primary_key_for(resource_class, attr)
11
+ reflection(resource_class, attr).association_primary_key
12
+ end
13
+
10
14
  def self.associated_class(resource_class, attr)
11
15
  reflection(resource_class, attr).klass
12
16
  end
@@ -49,10 +53,16 @@ module Administrate
49
53
  end
50
54
 
51
55
  def primary_key
56
+ # Deprecated, renamed `association_primary_key`
57
+ Administrate.warn_of_deprecated_method(self.class, :primary_key)
58
+ association_primary_key
59
+ end
60
+
61
+ def association_primary_key
52
62
  if option_given?(:primary_key)
53
63
  deprecated_option(:primary_key)
54
64
  else
55
- :id
65
+ self.class.association_primary_key_for(resource.class, attribute)
56
66
  end
57
67
  end
58
68
 
@@ -23,12 +23,15 @@ module Administrate
23
23
 
24
24
  def associated_resource_options
25
25
  candidate_resources.map do |resource|
26
- [display_candidate_resource(resource), resource.send(primary_key)]
26
+ [
27
+ display_candidate_resource(resource),
28
+ resource.send(association_primary_key),
29
+ ]
27
30
  end
28
31
  end
29
32
 
30
33
  def selected_option
31
- data && data.send(primary_key)
34
+ data&.send(association_primary_key)
32
35
  end
33
36
 
34
37
  def include_blank_option
@@ -30,21 +30,28 @@ module Administrate
30
30
  end
31
31
 
32
32
  def associated_resource_options
33
- candidate_resources.map do |resource|
34
- [display_candidate_resource(resource), resource.send(primary_key)]
33
+ candidate_resources.map do |associated_resource|
34
+ [
35
+ display_candidate_resource(associated_resource),
36
+ associated_resource.send(association_primary_key),
37
+ ]
35
38
  end
36
39
  end
37
40
 
38
41
  def selected_options
39
42
  return if data.empty?
40
43
 
41
- data.map { |object| object.send(primary_key) }
44
+ data.map { |object| object.send(association_primary_key) }
42
45
  end
43
46
 
44
47
  def limit
45
48
  options.fetch(:limit, DEFAULT_LIMIT)
46
49
  end
47
50
 
51
+ def paginate?
52
+ limit.respond_to?(:positive?) ? limit.positive? : limit.present?
53
+ end
54
+
48
55
  def permitted_attribute
49
56
  self.class.permitted_attribute(
50
57
  attribute,
@@ -53,12 +60,15 @@ module Administrate
53
60
  end
54
61
 
55
62
  def resources(page = 1, order = self.order)
56
- resources = order.apply(data).page(page).per(limit)
63
+ resources = order.apply(data)
64
+ if paginate?
65
+ resources = resources.page(page).per(limit)
66
+ end
57
67
  includes.any? ? resources.includes(*includes) : resources
58
68
  end
59
69
 
60
70
  def more_than_limit?
61
- data.count(:all) > limit
71
+ paginate? && data.count(:all) > limit
62
72
  end
63
73
 
64
74
  def data
@@ -30,7 +30,8 @@ module Administrate
30
30
  end
31
31
 
32
32
  def classes
33
- options.fetch(:classes, [])
33
+ klasses = options.fetch(:classes, [])
34
+ klasses.respond_to?(:call) ? klasses.call : klasses
34
35
  end
35
36
 
36
37
  private
@@ -8,22 +8,32 @@ module Administrate
8
8
  end
9
9
 
10
10
  def selectable_options
11
- collection
11
+ values =
12
+ if options.key?(:collection)
13
+ options.fetch(:collection)
14
+ elsif active_record_enum?
15
+ active_record_enum_values
16
+ else
17
+ []
18
+ end
19
+
20
+ if values.respond_to? :call
21
+ values = values.arity.positive? ? values.call(self) : values.call
22
+ end
23
+
24
+ values
12
25
  end
13
26
 
14
27
  def include_blank_option
15
28
  options.fetch(:include_blank, false)
16
29
  end
17
30
 
18
- private
19
-
20
- def collection
21
- values = options.fetch(:collection, [])
22
- if values.respond_to? :call
23
- return values.arity.positive? ? values.call(self) : values.call
24
- end
31
+ def active_record_enum?
32
+ resource.class.defined_enums.key?(attribute.to_s)
33
+ end
25
34
 
26
- @collection ||= values
35
+ def active_record_enum_values
36
+ resource.class.defined_enums[attribute.to_s].map(&:first)
27
37
  end
28
38
  end
29
39
  end
@@ -5,8 +5,10 @@ module Administrate
5
5
  @resource = resource
6
6
 
7
7
  case resource
8
- when Module, String, Symbol
8
+ when String, Symbol
9
9
  super("Not allowed to perform #{action.inspect} on #{resource.inspect}")
10
+ when Module
11
+ super("Not allowed to perform #{action.inspect} on #{resource.name}")
10
12
  else
11
13
  super(
12
14
  "Not allowed to perform #{action.inspect} on the given " +
@@ -52,11 +52,16 @@ module Administrate
52
52
  end
53
53
 
54
54
  def order_by_association(relation)
55
- return order_by_count(relation) if has_many_attribute?(relation)
56
-
57
- return order_by_attribute(relation) if belongs_to_attribute?(relation)
58
-
59
- relation
55
+ case relation_type(relation)
56
+ when :has_many
57
+ order_by_count(relation)
58
+ when :belongs_to
59
+ order_by_belongs_to(relation)
60
+ when :has_one
61
+ order_by_has_one(relation)
62
+ else
63
+ relation
64
+ end
60
65
  end
61
66
 
62
67
  def order_by_count(relation)
@@ -68,20 +73,36 @@ module Administrate
68
73
  reorder(Arel.sql(query))
69
74
  end
70
75
 
71
- def order_by_id(relation)
72
- relation.reorder(Arel.sql(order_by_id_query(relation)))
76
+ def order_by_belongs_to(relation)
77
+ if ordering_by_association_column?(relation)
78
+ order_by_attribute(relation)
79
+ else
80
+ order_by_id(relation)
81
+ end
73
82
  end
74
83
 
75
- def order_by_attribute(relation)
84
+ def order_by_has_one(relation)
76
85
  if ordering_by_association_column?(relation)
77
- relation.joins(
78
- attribute.to_sym,
79
- ).reorder(Arel.sql(order_by_attribute_query))
86
+ order_by_attribute(relation)
80
87
  else
81
- order_by_id(relation)
88
+ order_by_association_id(relation)
82
89
  end
83
90
  end
84
91
 
92
+ def order_by_attribute(relation)
93
+ relation.joins(
94
+ attribute.to_sym,
95
+ ).reorder(Arel.sql(order_by_attribute_query))
96
+ end
97
+
98
+ def order_by_id(relation)
99
+ relation.reorder(Arel.sql(order_by_id_query(relation)))
100
+ end
101
+
102
+ def order_by_association_id(relation)
103
+ relation.reorder(Arel.sql(order_by_association_id_query))
104
+ end
105
+
85
106
  def ordering_by_association_column?(relation)
86
107
  association_attribute &&
87
108
  column_exist?(
@@ -97,16 +118,16 @@ module Administrate
97
118
  "#{relation.table_name}.#{foreign_key(relation)} #{direction}"
98
119
  end
99
120
 
100
- def order_by_attribute_query
101
- "#{attribute.tableize}.#{association_attribute} #{direction}"
121
+ def order_by_association_id_query
122
+ "#{association_table_name}.id #{direction}"
102
123
  end
103
124
 
104
- def has_many_attribute?(relation)
105
- reflect_association(relation).macro == :has_many
125
+ def order_by_attribute_query
126
+ "#{association_table_name}.#{association_attribute} #{direction}"
106
127
  end
107
128
 
108
- def belongs_to_attribute?(relation)
109
- reflect_association(relation).macro == :belongs_to
129
+ def relation_type(relation)
130
+ reflect_association(relation).macro
110
131
  end
111
132
 
112
133
  def reflect_association(relation)
@@ -116,5 +137,9 @@ module Administrate
116
137
  def foreign_key(relation)
117
138
  reflect_association(relation).foreign_key
118
139
  end
140
+
141
+ def association_table_name
142
+ attribute.tableize
143
+ end
119
144
  end
120
145
  end
@@ -11,13 +11,6 @@ module Administrate
11
11
  attr_reader :resource
12
12
 
13
13
  def attributes(action = nil)
14
- action =
15
- case action
16
- when "update" then "edit"
17
- when "create" then "new"
18
- else action
19
- end
20
-
21
14
  dashboard.form_attributes(action).map do |attribute|
22
15
  attribute_field(dashboard, resource, attribute, :form)
23
16
  end
@@ -35,7 +35,8 @@ module Administrate
35
35
  end
36
36
 
37
37
  def controller_path_parts
38
- controller_path.split("/")[1..-1].map(&:singularize)
38
+ path_parts = controller_path.split("/")[1..-1]
39
+ path_parts << path_parts.pop.singularize
39
40
  end
40
41
 
41
42
  attr_reader :controller_path
@@ -31,7 +31,7 @@ module Administrate
31
31
  private
32
32
 
33
33
  def filter?(word)
34
- valid_filters&.any? { |filter| word.match?(/^#{filter}:\w*$/) }
34
+ valid_filters&.any? { |filter| word.match?(/^#{filter}:/) }
35
35
  end
36
36
 
37
37
  def parse_query(query)
@@ -1,3 +1,3 @@
1
1
  module Administrate
2
- VERSION = "0.18.0".freeze
2
+ VERSION = "0.19.0".freeze
3
3
  end
@@ -5,7 +5,12 @@ require "administrate/namespace"
5
5
  module Administrate
6
6
  class ViewGenerator < Rails::Generators::Base
7
7
  include Administrate::GeneratorHelpers
8
- class_option :namespace, type: :string, default: :admin
8
+ class_option(
9
+ :namespace,
10
+ type: :string,
11
+ desc: "Namespace where the admin dashboards live",
12
+ default: "admin",
13
+ )
9
14
 
10
15
  def self.template_source_path
11
16
  File.expand_path(
@@ -27,7 +27,12 @@ module Administrate
27
27
  COLLECTION_ATTRIBUTE_LIMIT = 4
28
28
  READ_ONLY_ATTRIBUTES = %w[id created_at updated_at]
29
29
 
30
- class_option :namespace, type: :string, default: :admin
30
+ class_option(
31
+ :namespace,
32
+ type: :string,
33
+ desc: "Namespace where the admin dashboards live",
34
+ default: "admin",
35
+ )
31
36
 
32
37
  source_root File.expand_path("../templates", __FILE__)
33
38
 
@@ -36,11 +36,11 @@ module <%= namespace.to_s.camelize %>
36
36
  #
37
37
  # def resource_params
38
38
  # params.require(resource_class.model_name.param_key).
39
- # permit(dashboard.permitted_attributes).
39
+ # permit(dashboard.permitted_attributes(action_name)).
40
40
  # transform_values { |value| value == "" ? nil : value }
41
41
  # end
42
42
 
43
- # See https://administrate-prototype.herokuapp.com/customizing_controller_actions
43
+ # See https://administrate-demo.herokuapp.com/customizing_controller_actions
44
44
  # for more information
45
45
  end
46
46
  end
@@ -14,7 +14,12 @@ module Administrate
14
14
  include Administrate::GeneratorHelpers
15
15
  source_root File.expand_path("../templates", __FILE__)
16
16
 
17
- class_option :namespace, type: :string, default: "admin"
17
+ class_option(
18
+ :namespace,
19
+ type: :string,
20
+ desc: "Namespace where the admin dashboards will live",
21
+ default: "admin",
22
+ )
18
23
 
19
24
  def run_routes_generator
20
25
  if dashboard_resources.none?
@@ -7,13 +7,19 @@ end
7
7
  require "rails/generators/base"
8
8
  require "administrate/generator_helpers"
9
9
  require "administrate/namespace"
10
+ require "generators/administrate/test_record"
10
11
 
11
12
  module Administrate
12
13
  module Generators
13
14
  class RoutesGenerator < Rails::Generators::Base
14
15
  include Administrate::GeneratorHelpers
15
16
  source_root File.expand_path("../templates", __FILE__)
16
- class_option :namespace, type: :string, default: "admin"
17
+ class_option(
18
+ :namespace,
19
+ type: :string,
20
+ desc: "Namespace where the admin dashboards live",
21
+ default: "admin",
22
+ )
17
23
 
18
24
  def insert_dashboard_routes
19
25
  if valid_dashboard_models.any?
@@ -55,7 +61,10 @@ module Administrate
55
61
  end
56
62
 
57
63
  def database_models
58
- ActiveRecord::Base.descendants.reject(&:abstract_class?)
64
+ ActiveRecord::Base.descendants.
65
+ reject(&:abstract_class?).
66
+ reject { |k| k < Administrate::Generators::TestRecord }.
67
+ sort_by(&:to_s)
59
68
  end
60
69
 
61
70
  def invalid_dashboard_models
@@ -0,0 +1,21 @@
1
+ module Administrate
2
+ module Generators
3
+ # This class serves only to work around a strange behaviour in Rails 7
4
+ # with Ruby 3.
5
+ #
6
+ # After running the spec for DashboardGenerator, the fake models that
7
+ # it generates (eg: Foo, Shipment) linger around despite being removed
8
+ # explicitly. This causes RouteGenerator to take them into
9
+ # account and generate routes for them, which its spec doesn't expect,
10
+ # causing a spec failure.
11
+ #
12
+ # To avoid this, the spec for DashboardGenerator defines its fake models
13
+ # as children of TestRecord. Then RoutesGenerator explicitly filters
14
+ # child classes of TestRecord when figuring out what models exist.
15
+ #
16
+ # Discussion at https://github.com/thoughtbot/administrate/pull/2324
17
+ class TestRecord < ApplicationRecord
18
+ self.abstract_class = true
19
+ end
20
+ end
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: administrate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Charlton
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-08-12 00:00:00.000000000 Z
12
+ date: 2023-07-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -132,6 +132,7 @@ files:
132
132
  - Rakefile
133
133
  - app/assets/javascripts/administrate/application.js
134
134
  - app/assets/javascripts/administrate/components/associative.js
135
+ - app/assets/javascripts/administrate/components/select.js
135
136
  - app/assets/javascripts/administrate/components/table.js
136
137
  - app/assets/stylesheets/administrate/application.scss
137
138
  - app/assets/stylesheets/administrate/base/_forms.scss
@@ -314,6 +315,7 @@ files:
314
315
  - lib/generators/administrate/install/templates/application_controller.rb.erb
315
316
  - lib/generators/administrate/routes/routes_generator.rb
316
317
  - lib/generators/administrate/routes/templates/routes.rb.erb
318
+ - lib/generators/administrate/test_record.rb
317
319
  - lib/generators/administrate/views/edit_generator.rb
318
320
  - lib/generators/administrate/views/field_generator.rb
319
321
  - lib/generators/administrate/views/form_generator.rb
@@ -343,7 +345,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
343
345
  - !ruby/object:Gem::Version
344
346
  version: '0'
345
347
  requirements: []
346
- rubygems_version: 3.1.6
348
+ rubygems_version: 3.4.14
347
349
  signing_key:
348
350
  specification_version: 4
349
351
  summary: A Rails engine for creating super-flexible admin dashboards