administrate 0.17.0 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +0 -2
  3. data/app/assets/javascripts/administrate/application.js +0 -2
  4. data/app/assets/stylesheets/administrate/application.scss +0 -1
  5. data/app/assets/stylesheets/administrate/base/_forms.scss +1 -1
  6. data/app/assets/stylesheets/administrate/components/_buttons.scss +9 -0
  7. data/app/assets/stylesheets/administrate/components/_flashes.scss +2 -2
  8. data/app/assets/stylesheets/administrate/library/_variables.scss +1 -1
  9. data/app/controllers/administrate/application_controller.rb +78 -13
  10. data/app/controllers/concerns/administrate/punditize.rb +4 -2
  11. data/app/helpers/administrate/application_helper.rb +24 -6
  12. data/app/views/administrate/application/_collection.html.erb +3 -3
  13. data/app/views/administrate/application/_collection_header_actions.html.erb +2 -2
  14. data/app/views/administrate/application/_collection_item_actions.html.erb +4 -4
  15. data/app/views/administrate/application/_index_header.html.erb +1 -1
  16. data/app/views/administrate/application/_navigation.html.erb +1 -1
  17. data/app/views/administrate/application/_pagination.html.erb +1 -0
  18. data/app/views/administrate/application/edit.html.erb +1 -1
  19. data/app/views/administrate/application/index.html.erb +1 -1
  20. data/app/views/administrate/application/show.html.erb +9 -1
  21. data/app/views/fields/belongs_to/_index.html.erb +1 -1
  22. data/app/views/fields/belongs_to/_show.html.erb +1 -1
  23. data/app/views/fields/date/_form.html.erb +1 -3
  24. data/app/views/fields/date_time/_form.html.erb +1 -3
  25. data/app/views/fields/has_many/_index.html.erb +1 -1
  26. data/app/views/fields/has_one/_form.html.erb +1 -1
  27. data/app/views/fields/has_one/_index.html.erb +2 -1
  28. data/app/views/fields/has_one/_show.html.erb +3 -2
  29. data/app/views/fields/polymorphic/_index.html.erb +2 -1
  30. data/app/views/fields/polymorphic/_show.html.erb +1 -1
  31. data/app/views/fields/time/_form.html.erb +2 -3
  32. data/app/views/fields/url/_index.html.erb +1 -1
  33. data/app/views/fields/url/_show.html.erb +1 -1
  34. data/config/locales/administrate.de.yml +2 -2
  35. data/config/locales/administrate.sl.yml +30 -0
  36. data/docs/adding_controllers_without_related_model.md +2 -2
  37. data/docs/authorization.md +25 -12
  38. data/docs/customizing_controller_actions.md +11 -6
  39. data/docs/customizing_dashboards.md +17 -2
  40. data/docs/getting_started.md +1 -1
  41. data/docs/guides/customising_search.md +149 -0
  42. data/docs/guides/hiding_dashboards_from_sidebar.md +4 -2
  43. data/docs/guides/scoping_has_many_relations.md +27 -0
  44. data/docs/guides.md +3 -1
  45. data/lib/administrate/base_dashboard.rb +14 -0
  46. data/lib/administrate/engine.rb +2 -2
  47. data/lib/administrate/field/associative.rb +7 -7
  48. data/lib/administrate/field/base.rb +4 -0
  49. data/lib/administrate/field/belongs_to.rb +4 -0
  50. data/lib/administrate/field/deferred.rb +4 -0
  51. data/lib/administrate/field/has_one.rb +4 -0
  52. data/lib/administrate/field/url.rb +4 -0
  53. data/lib/administrate/not_authorized_error.rb +18 -0
  54. data/lib/administrate/order.rb +35 -5
  55. data/lib/administrate/page/base.rb +4 -0
  56. data/lib/administrate/version.rb +1 -1
  57. data/lib/administrate.rb +18 -0
  58. data/lib/generators/administrate/dashboard/dashboard_generator.rb +14 -1
  59. metadata +7 -34
  60. data/app/assets/javascripts/administrate/components/date_time_picker.js +0 -14
  61. data/config/i18n-tasks.yml +0 -18
  62. data/config/routes.rb +0 -2
  63. data/config/unicorn.rb +0 -25
@@ -49,23 +49,36 @@ end
49
49
 
50
50
  ## Authorization without Pundit
51
51
 
52
- If you use a different authorization library, or you want to roll your own,
53
- you just need to override a few methods in your controllers or
54
- `Admin::ApplicationController`. For example:
52
+ Pundit is not necessary to implement authorization within Administrate. It is
53
+ simply a common solution that many in the community use, and for this reason
54
+ Administrate provides a plugin to work with it. However you can use a different
55
+ solution or roll out your own.
56
+
57
+ To integrate a different authorization solution, you will need to
58
+ implement some methods in `Admin::ApplicationController`
59
+ or its subclasses.
60
+
61
+ These are the methods to override, with examples:
55
62
 
56
63
  ```ruby
57
- # Limit the scope of the given resource
64
+ # Used in listings, such as the `index` actions. It
65
+ # restricts the scope of records that a user can access.
66
+ # Returns an ActiveRecord scope.
58
67
  def scoped_resource
59
68
  super.where(user: current_user)
60
69
  end
61
70
 
62
- # Raise an exception if the user is not permitted to access this resource
63
- def authorize_resource(resource)
64
- raise "Erg!" unless show_action?(params[:action], resource)
65
- end
66
-
67
- # Hide links to actions if the user is not allowed to do them
68
- def show_action?(action, resource)
69
- current_user.can? action, resource
71
+ # Return true if the current user can access the given
72
+ # resource, false otherwise.
73
+ def authorized_action?(resource, action)
74
+ current_user.can?(resource, action)
70
75
  end
71
76
  ```
77
+
78
+ Additionally, the method `authorize_resource(resource)`
79
+ should throw an exception if the current user is not
80
+ allowed to access the given resource. Normally
81
+ you wouldn't need to override it, as the default
82
+ implementation uses `authorized_action?` to produce the
83
+ correct behaviour. However you may still want to override it
84
+ if you want to raise a custom error type.
@@ -46,17 +46,22 @@ end
46
46
 
47
47
  ## Customizing Actions
48
48
 
49
- To enable or disable certain actions you could override `valid_action?` method in your dashboard controller like this:
49
+ To disable certain actions globally, you can disable their
50
+ routes in `config/routes.rb`, using the usual Rails
51
+ facilities for this. For example:
50
52
 
51
53
  ```ruby
52
- # disable 'edit' and 'destroy' links
53
- def valid_action?(name, resource = resource_class)
54
- %w[edit destroy].exclude?(name.to_s) && super
54
+ Rails.application.routes.draw do
55
+ # ...
56
+ namespace :admin do
57
+ # ...
58
+
59
+ # Payments can only be listed or displayed
60
+ resources :payments, only: [:index, :show]
61
+ end
55
62
  end
56
63
  ```
57
64
 
58
- Action is one of `new`, `edit`, `show`, `destroy`.
59
-
60
65
  ## Customizing Default Sorting
61
66
 
62
67
  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:
@@ -77,8 +77,9 @@ which are specified through the `.with_options` class method:
77
77
 
78
78
  **Field::BelongsTo**
79
79
 
80
- `:order` - Specifies the order of the dropdown menu, can be ordered by more
81
- than one column. e.g.: `"name, email DESC"`.
80
+ `:order` - Specifies the column used to order the records. It will apply both in
81
+ the table views and in the dropdown menu on the record forms.
82
+ You can set multiple columns as well with direction. E.g.: `"name, email DESC"`.
82
83
 
83
84
  `:scope` - Specifies a custom scope inside a callable. Useful for preloading.
84
85
  Example: `.with_options(scope: -> { MyModel.includes(:rel).limit(5) })`
@@ -220,6 +221,17 @@ objects to display as.
220
221
  `:collection` - Specify the options shown on the select field. It accept either
221
222
  an array or an object responding to `:call`. Defaults to `[]`.
222
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
+
226
+ For example:
227
+
228
+ ```ruby
229
+ currency = Field::Select.with_options(
230
+ collection: [ ['usd', 'Dollar'], ['eur', 'Euro'], ['yen', 'Yen'] ]
231
+ )
232
+
233
+ ```
234
+
223
235
  `:searchable` - Specify if the attribute should be considered when searching.
224
236
  Default is `true`.
225
237
 
@@ -250,6 +262,9 @@ Default is `true`.
250
262
  `:truncate` - Set the number of characters to display in the index view.
251
263
  Defaults to `50`.
252
264
 
265
+ `:html_options` - Specify anchor tag attributes (e.g., `target="_blank"`).
266
+ Defaults is `{}`.
267
+
253
268
  **Field::Password**
254
269
 
255
270
  `:searchable` - Specify if the attribute should be considered when searching.
@@ -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.6 and up.
6
+ applications version 5.0 or greater. We support Ruby 2.7 and up.
7
7
 
8
8
  First, add the following to your Gemfile:
9
9
 
@@ -0,0 +1,149 @@
1
+ ---
2
+ title: Customising the search
3
+ ---
4
+
5
+ Administrate dashboards provide a search function, but it is quite basic.
6
+ Things like search across complex associations, inside JSON columns, or outside
7
+ the database (eg: an Elasticsearch index) are not possible out of the box.
8
+
9
+ Fortunately, Administrate is just Rails, so you can use your existing Rails
10
+ knowledge to customize the search feature. Let's look into that.
11
+
12
+ ## In short
13
+
14
+ Override the `filter_resources` method in your admin controllers in order
15
+ to customize the search.
16
+
17
+ It has two parameters:
18
+
19
+ * `resources`: an ActiveRecord relation for the model on whose dashboard the
20
+ search originated.
21
+ * `search_term:`: a string representing the search query entered by the user.
22
+
23
+ Return an ActiveRecord relation for the same model as `resources`, matching
24
+ the desired search results.
25
+
26
+ ## In more detail
27
+
28
+ When you install Administrate in your application, it generates an admin
29
+ controller for each of your ActiveRecord models, as well as a base controller
30
+ that all of these inherit from.
31
+
32
+ For example, if you have two ActiveRecord models: `Person` and `Address`,
33
+ running `rails generate administrate:install` will get you the following
34
+ files (plus others that are not relevant here):
35
+
36
+ * `app/controllers/admin/people_controller.rb`
37
+ * `app/controllers/admin/addresses_controller.rb`
38
+ * `app/controllers/admin/application_controller.rb`
39
+
40
+ By default, searches are handled by the `index` action of the controller that
41
+ the user was visiting when they performed the search. For example, if a user
42
+ is visiting the People dashboard and submits a search, the user is sent to
43
+ the path `/admin/people?search=<search query>`. This is routed to
44
+ `Admin::PeopleController#index`, where the search query can be read as
45
+ `params[:search]`.
46
+
47
+ By default, these controllers are empty. Administrate's code is implemented
48
+ at `Administrate::ApplicationController`, from which all inherit. This is
49
+ where search is implemented. You can read the code yourself at:
50
+ https://github.com/thoughtbot/administrate/blob/main/app/controllers/administrate/application_controller.rb.
51
+
52
+ It is in the linked code that you can see what Administrate actually does.
53
+ For example, this is the `index` action at the time of writing these lines:
54
+
55
+ ```ruby
56
+ def index
57
+ authorize_resource(resource_class)
58
+ search_term = params[:search].to_s.strip
59
+ resources = filter_resources(scoped_resource, search_term: search_term)
60
+ resources = apply_collection_includes(resources)
61
+ resources = order.apply(resources)
62
+ resources = resources.page(params[:_page]).per(records_per_page)
63
+ page = Administrate::Page::Collection.new(dashboard, order: order)
64
+
65
+ render locals: {
66
+ resources: resources,
67
+ search_term: search_term,
68
+ page: page,
69
+ show_search_bar: show_search_bar?,
70
+ }
71
+ end
72
+ ```
73
+
74
+ What the above does is applying a few transforms
75
+ to the variable `resources`, filtering it, applying includes for associations,
76
+ ordering the results, paginating them, and finally handing them over to the
77
+ template in order to be rendered. All this is pretty standard Rails, although
78
+ split into individual steps that can be overriden by developers in order
79
+ to add customizations, and ultimately wrapped in an instance of
80
+ `Administrate::Page::Collection` which will read your dashboard definitions
81
+ and figure out what fields you want displayed.
82
+
83
+ It is the filtering part where the search is implemented. You will notice the
84
+ `filter_resources` method, which takes a parameter `search_term`. This is what
85
+ this method looks like at the moment:
86
+
87
+ ```ruby
88
+ def filter_resources(resources, search_term:)
89
+ Administrate::Search.new(
90
+ resources,
91
+ dashboard,
92
+ search_term,
93
+ ).run
94
+ end
95
+ ```
96
+
97
+ The class `Administrate::Search` implements the default search facilities
98
+ within Administrate... but you do not have to worry about it! You can ignore
99
+ it and implement your own search in `filter_resources`. For example, you
100
+ could write your own version in your controller, to override Administrate's
101
+ own. Something like this:
102
+
103
+ ```ruby
104
+ def filter_resources(resources, search_term:)
105
+ resources.where(first_name: search_term)
106
+ .or(People.where(last_name: search_term))
107
+ end
108
+ ```
109
+
110
+ It can be as complex (or simple) as you want, as long as the return value
111
+ of the method is an ActiveRecord relation.
112
+
113
+ What if you do not want to search in the DB? For example, say that your records
114
+ are indexed by Elasticsearch or something like that. You can still search
115
+ in your external index and convert the results to an ActiveRecord relation.
116
+ Here's an example:
117
+
118
+ ```ruby
119
+ def filter_resources(resources, search_term:)
120
+ # Run the search term through your search facility
121
+ results = MySuperDuperSearchSystem.search_people(search_term)
122
+
123
+ # Collect the ids of the results. This assumes that they will
124
+ # be the same ones as in the DB.
125
+ record_ids = results.entries.map(&:id)
126
+
127
+ # Use the ids to create an ActiveRecord relation and return it
128
+ People.where(id: record_ids)
129
+ end
130
+ ```
131
+
132
+ Note though: the records must still exist in the DB. Administrate does
133
+ require ActiveRecord in order to show tables, and to display, create and edit
134
+ records.
135
+
136
+ ## A working example
137
+
138
+ The [Administrate demo app](/admin)
139
+ includes an example of custom search in the "Log Entries" dashboard.
140
+ In this app, each `LogEntry` instance has a polymorphic `belongs_to`
141
+ association to a `:logeable`. Logeables are other models for which logs can be
142
+ created. At the moment these are `Order` and `Customer`.
143
+
144
+ Administrate's default search is not able to search across polymorphic
145
+ associations, and therefore it is not possible to search logs by the contents
146
+ of their logeables. Fortunately this can be fixed with a custom search. This is
147
+ done by implementing `Admin::LogEntriesController#filter_resources` to override
148
+ the default search. You can see the code at
149
+ https://github.com/thoughtbot/administrate/blob/main/spec/example_app/app/controllers/admin/log_entries_controller.rb
@@ -2,7 +2,8 @@
2
2
  title: Hiding Dashboards from the Sidebar
3
3
  ---
4
4
 
5
- Resources can be removed form the sidebar by removing their index action from the routes. For example:
5
+ Resources can be removed from the sidebar by removing their `index` action
6
+ from the routes. For example:
6
7
 
7
8
  ```ruby
8
9
  # config/routes.rb
@@ -16,4 +17,5 @@ Rails.application.routes.draw do
16
17
  end
17
18
  ```
18
19
 
19
- In this case, only Orders and Products will appear in the sidebar, while Line Items can still appear as an association.
20
+ In this case, only Orders and Products will appear in the sidebar, while
21
+ Line Items can still appear as an association.
@@ -0,0 +1,27 @@
1
+ ---
2
+ title: Scoping HasMany Relations
3
+ ---
4
+
5
+ To show a subset of a has_many relationship, create a new [has_many](https://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many) relationship in your model (using the `scope` argument) and add it to the model's dashboard.
6
+
7
+ ## Creating a scoped has_many relationship
8
+
9
+ Models can define subsets of a `has_many` relationship by passing a callable (i.e. proc or lambda) as its second argument.
10
+
11
+ ```ruby
12
+ class Customer < ApplicationRecord
13
+ has_many :orders
14
+ has_many :processed_orders, ->{ where(processed: true) }, class_name: "Order"
15
+ ```
16
+
17
+ Since ActiveRecord infers the class name from the first argument, the new `has_many` relation needs to specify the model using the `class_name` option.
18
+
19
+ ## Add new relationship to dashboard
20
+
21
+ Your new scoped relation can be used in the dashboard just like the original `HasMany`. Notice the new field needs to specifiy the class name as an option like you did in the model.
22
+
23
+ ```ruby
24
+ ATTRIBUTE_TYPES = {
25
+ orders: Field::HasMany,
26
+ processed_orders: Field::HasMany.with_options(class_name: 'Order')
27
+ ```
data/docs/guides.md CHANGED
@@ -2,4 +2,6 @@
2
2
  title: Guides
3
3
  ---
4
4
 
5
- * [Hiding Dashboards from the Sidebar](./guides/hiding_dashboards_from_sidebar)
5
+ - [Hiding Dashboards from the Sidebar](./guides/hiding_dashboards_from_sidebar)
6
+ - [Customising the search](./guides/customising_search)
7
+ - [Scoping HasMany Relations](./guides/scoping_has_many_relations.md)
@@ -94,9 +94,15 @@ module Administrate
94
94
  end
95
95
 
96
96
  def item_includes
97
+ # Deprecated, internal usage has moved to #item_associations
98
+ Administrate.warn_of_deprecated_method(self.class, :item_includes)
97
99
  attribute_includes(show_page_attributes)
98
100
  end
99
101
 
102
+ def item_associations
103
+ attribute_associated(show_page_attributes)
104
+ end
105
+
100
106
  private
101
107
 
102
108
  def attribute_not_found_message(attr)
@@ -104,6 +110,14 @@ module Administrate
104
110
  end
105
111
 
106
112
  def attribute_includes(attributes)
113
+ attributes.map do |key|
114
+ field = attribute_type_for(key)
115
+
116
+ key if field.eager_load?
117
+ end.compact
118
+ end
119
+
120
+ def attribute_associated(attributes)
107
121
  attributes.map do |key|
108
122
  field = attribute_type_for(key)
109
123
 
@@ -1,11 +1,11 @@
1
- require "datetime_picker_rails"
2
1
  require "jquery-rails"
3
2
  require "kaminari"
4
- require "momentjs-rails"
5
3
  require "sassc-rails"
6
4
  require "selectize-rails"
7
5
  require "sprockets/railtie"
8
6
 
7
+ require "administrate/namespace/resource"
8
+ require "administrate/not_authorized_error"
9
9
  require "administrate/page/form"
10
10
  require "administrate/page/show"
11
11
  require "administrate/page/collection"
@@ -12,7 +12,7 @@ module Administrate
12
12
  end
13
13
 
14
14
  def self.associated_class_name(resource_class, attr)
15
- reflection(resource_class, attr).class_name
15
+ associated_class(resource_class, attr).name
16
16
  end
17
17
 
18
18
  def self.reflection(resource_class, attr)
@@ -31,12 +31,6 @@ module Administrate
31
31
  end
32
32
  end
33
33
 
34
- private
35
-
36
- def associated_dashboard
37
- "#{associated_class_name}Dashboard".constantize.new
38
- end
39
-
40
34
  def associated_class_name
41
35
  if option_given?(:class_name)
42
36
  deprecated_option(:class_name)
@@ -48,6 +42,12 @@ module Administrate
48
42
  end
49
43
  end
50
44
 
45
+ private
46
+
47
+ def associated_dashboard
48
+ "#{associated_class_name}Dashboard".constantize.new
49
+ end
50
+
51
51
  def primary_key
52
52
  if option_given?(:primary_key)
53
53
  deprecated_option(:primary_key)
@@ -16,6 +16,10 @@ module Administrate
16
16
  self < Associative
17
17
  end
18
18
 
19
+ def self.eager_load?
20
+ false
21
+ end
22
+
19
23
  def self.searchable?
20
24
  false
21
25
  end
@@ -13,6 +13,10 @@ module Administrate
13
13
  end
14
14
  end
15
15
 
16
+ def self.eager_load?
17
+ true
18
+ end
19
+
16
20
  def permitted_attribute
17
21
  foreign_key
18
22
  end
@@ -25,6 +25,10 @@ module Administrate
25
25
  deferred_class.associative?
26
26
  end
27
27
 
28
+ def eager_load?
29
+ deferred_class.eager_load?
30
+ end
31
+
28
32
  def searchable?
29
33
  options.fetch(:searchable, deferred_class.searchable?)
30
34
  end
@@ -26,6 +26,10 @@ module Administrate
26
26
  { "#{attr}_attributes": related_dashboard_attributes }
27
27
  end
28
28
 
29
+ def self.eager_load?
30
+ true
31
+ end
32
+
29
33
  def nested_form
30
34
  @nested_form ||= Administrate::Page::Form.new(
31
35
  resolver.dashboard_class.new,
@@ -11,6 +11,10 @@ module Administrate
11
11
  data.to_s[0...truncation_length]
12
12
  end
13
13
 
14
+ def html_options
15
+ @options[:html_options] || {}
16
+ end
17
+
14
18
  private
15
19
 
16
20
  def truncation_length
@@ -0,0 +1,18 @@
1
+ module Administrate
2
+ class NotAuthorizedError < StandardError
3
+ def initialize(action:, resource:)
4
+ @action = action
5
+ @resource = resource
6
+
7
+ case resource
8
+ when Module, String, Symbol
9
+ super("Not allowed to perform #{action.inspect} on #{resource.inspect}")
10
+ else
11
+ super(
12
+ "Not allowed to perform #{action.inspect} on the given " +
13
+ resource.class.name
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,8 +1,9 @@
1
1
  module Administrate
2
2
  class Order
3
- def initialize(attribute = nil, direction = nil)
3
+ def initialize(attribute = nil, direction = nil, association_attribute: nil)
4
4
  @attribute = attribute
5
5
  @direction = sanitize_direction(direction)
6
+ @association_attribute = association_attribute
6
7
  end
7
8
 
8
9
  def apply(relation)
@@ -12,7 +13,7 @@ module Administrate
12
13
  order = "#{relation.table_name}.#{attribute} #{direction}"
13
14
 
14
15
  return relation.reorder(Arel.sql(order)) if
15
- relation.columns_hash.keys.include?(attribute.to_s)
16
+ column_exist?(relation, attribute)
16
17
 
17
18
  relation
18
19
  end
@@ -32,7 +33,7 @@ module Administrate
32
33
 
33
34
  private
34
35
 
35
- attr_reader :attribute
36
+ attr_reader :attribute, :association_attribute
36
37
 
37
38
  def sanitize_direction(direction)
38
39
  %w[asc desc].include?(direction.to_s) ? direction.to_sym : :asc
@@ -53,7 +54,7 @@ module Administrate
53
54
  def order_by_association(relation)
54
55
  return order_by_count(relation) if has_many_attribute?(relation)
55
56
 
56
- return order_by_id(relation) if belongs_to_attribute?(relation)
57
+ return order_by_attribute(relation) if belongs_to_attribute?(relation)
57
58
 
58
59
  relation
59
60
  end
@@ -68,7 +69,36 @@ module Administrate
68
69
  end
69
70
 
70
71
  def order_by_id(relation)
71
- relation.reorder("#{foreign_key(relation)} #{direction}")
72
+ relation.reorder(Arel.sql(order_by_id_query(relation)))
73
+ end
74
+
75
+ def order_by_attribute(relation)
76
+ if ordering_by_association_column?(relation)
77
+ relation.joins(
78
+ attribute.to_sym,
79
+ ).reorder(Arel.sql(order_by_attribute_query))
80
+ else
81
+ order_by_id(relation)
82
+ end
83
+ end
84
+
85
+ def ordering_by_association_column?(relation)
86
+ association_attribute &&
87
+ column_exist?(
88
+ reflect_association(relation).klass, association_attribute.to_sym
89
+ )
90
+ end
91
+
92
+ def column_exist?(table, column_name)
93
+ table.columns_hash.key?(column_name.to_s)
94
+ end
95
+
96
+ def order_by_id_query(relation)
97
+ "#{relation.table_name}.#{foreign_key(relation)} #{direction}"
98
+ end
99
+
100
+ def order_by_attribute_query
101
+ "#{attribute.tableize}.#{association_attribute} #{direction}"
72
102
  end
73
103
 
74
104
  def has_many_attribute?(relation)
@@ -23,6 +23,10 @@ module Administrate
23
23
  dashboard.try(:item_includes) || []
24
24
  end
25
25
 
26
+ def item_associations
27
+ dashboard.try(:item_associations) || []
28
+ end
29
+
26
30
  private
27
31
 
28
32
  def attribute_field(dashboard, resource, attribute_name, page)
@@ -1,3 +1,3 @@
1
1
  module Administrate
2
- VERSION = "0.17.0".freeze
2
+ VERSION = "0.18.0".freeze
3
3
  end
data/lib/administrate.rb CHANGED
@@ -20,4 +20,22 @@ module Administrate
20
20
  "if you think otherwise.",
21
21
  )
22
22
  end
23
+
24
+ def self.warn_of_deprecated_method(klass, method)
25
+ ActiveSupport::Deprecation.warn(
26
+ "The method #{klass}##{method} is deprecated. " +
27
+ "If you are seeing this message you are probably " +
28
+ "using a dashboard that depends explicitly on it. " +
29
+ "Please make sure you update it to a version that " +
30
+ "does not use a deprecated API",
31
+ )
32
+ end
33
+
34
+ def self.warn_of_deprecated_authorization_method(method)
35
+ ActiveSupport::Deprecation.warn(
36
+ "The method `#{method}` is deprecated. " +
37
+ "Please use `accessible_action?` instead, " +
38
+ "or see the documentation for other options.",
39
+ )
40
+ end
23
41
  end
@@ -53,9 +53,22 @@ module Administrate
53
53
  end
54
54
 
55
55
  def attributes
56
- klass.reflections.keys +
56
+ attrs = (
57
+ klass.reflections.keys +
57
58
  klass.columns.map(&:name) -
58
59
  redundant_attributes
60
+ )
61
+
62
+ primary_key = attrs.delete(klass.primary_key)
63
+ created_at = attrs.delete("created_at")
64
+ updated_at = attrs.delete("updated_at")
65
+
66
+ [
67
+ primary_key,
68
+ *attrs.sort,
69
+ created_at,
70
+ updated_at,
71
+ ].compact
59
72
  end
60
73
 
61
74
  def form_attributes