administrate 0.12.0 → 0.20.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) 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/javascripts/administrate/components/associative.js +5 -0
  5. data/app/assets/javascripts/administrate/components/select.js +3 -0
  6. data/app/assets/javascripts/administrate/components/table.js +1 -1
  7. data/app/assets/stylesheets/administrate/application.scss +0 -1
  8. data/app/assets/stylesheets/administrate/base/_forms.scss +1 -1
  9. data/app/assets/stylesheets/administrate/base/_tables.scss +3 -0
  10. data/app/assets/stylesheets/administrate/components/_attributes.scss +4 -3
  11. data/app/assets/stylesheets/administrate/components/_buttons.scss +20 -0
  12. data/app/assets/stylesheets/administrate/components/_cells.scss +2 -0
  13. data/app/assets/stylesheets/administrate/components/_field-unit.scss +24 -4
  14. data/app/assets/stylesheets/administrate/components/_flashes.scss +2 -10
  15. data/app/assets/stylesheets/administrate/components/_main-content.scss +1 -0
  16. data/app/assets/stylesheets/administrate/components/_navigation.scss +2 -3
  17. data/app/assets/stylesheets/administrate/library/_variables.scss +11 -9
  18. data/app/controllers/administrate/application_controller.rb +127 -25
  19. data/app/controllers/concerns/administrate/punditize.rb +44 -20
  20. data/app/helpers/administrate/application_helper.rb +56 -20
  21. data/app/views/administrate/application/_collection.html.erb +26 -29
  22. data/app/views/administrate/application/_collection_header_actions.html.erb +4 -0
  23. data/app/views/administrate/application/_collection_item_actions.html.erb +17 -0
  24. data/app/views/administrate/application/_flashes.html.erb +1 -0
  25. data/app/views/administrate/application/_form.html.erb +20 -5
  26. data/app/views/administrate/application/_icons.html.erb +1 -1
  27. data/app/views/administrate/application/_index_header.html.erb +28 -0
  28. data/app/views/administrate/application/_navigation.html.erb +6 -4
  29. data/app/views/administrate/application/_pagination.html.erb +1 -0
  30. data/app/views/administrate/application/edit.html.erb +2 -2
  31. data/app/views/administrate/application/index.html.erb +9 -29
  32. data/app/views/administrate/application/new.html.erb +1 -1
  33. data/app/views/administrate/application/show.html.erb +28 -12
  34. data/app/views/fields/belongs_to/_form.html.erb +3 -3
  35. data/app/views/fields/belongs_to/_index.html.erb +1 -1
  36. data/app/views/fields/belongs_to/_show.html.erb +1 -1
  37. data/app/views/fields/date/_form.html.erb +22 -0
  38. data/app/views/fields/date/_index.html.erb +21 -0
  39. data/app/views/fields/date/_show.html.erb +21 -0
  40. data/app/views/fields/date_time/_form.html.erb +1 -3
  41. data/app/views/fields/has_many/_index.html.erb +1 -1
  42. data/app/views/fields/has_many/_show.html.erb +2 -1
  43. data/app/views/fields/has_one/_form.html.erb +15 -5
  44. data/app/views/fields/has_one/_index.html.erb +3 -2
  45. data/app/views/fields/has_one/_show.html.erb +22 -15
  46. data/app/views/fields/number/_form.html.erb +1 -1
  47. data/app/views/fields/polymorphic/_index.html.erb +2 -1
  48. data/app/views/fields/polymorphic/_show.html.erb +1 -1
  49. data/app/views/fields/select/_form.html.erb +9 -8
  50. data/app/views/fields/string/_show.html.erb +2 -2
  51. data/app/views/fields/text/_show.html.erb +2 -3
  52. data/app/views/fields/time/_form.html.erb +2 -2
  53. data/app/views/fields/time/_index.html.erb +3 -1
  54. data/app/views/fields/time/_show.html.erb +3 -1
  55. data/app/views/fields/url/_index.html.erb +2 -2
  56. data/app/views/fields/url/_show.html.erb +2 -2
  57. data/app/views/layouts/administrate/application.html.erb +2 -1
  58. data/config/locales/administrate.ar.yml +2 -0
  59. data/config/locales/administrate.bs.yml +2 -0
  60. data/config/locales/administrate.ca.yml +2 -0
  61. data/config/locales/administrate.da.yml +2 -0
  62. data/config/locales/administrate.de.yml +4 -2
  63. data/config/locales/administrate.en.yml +2 -0
  64. data/config/locales/administrate.es.yml +2 -0
  65. data/config/locales/administrate.fi.yml +30 -0
  66. data/config/locales/administrate.fr.yml +4 -2
  67. data/config/locales/administrate.id.yml +2 -0
  68. data/config/locales/administrate.it.yml +2 -0
  69. data/config/locales/administrate.ja.yml +7 -5
  70. data/config/locales/administrate.ko.yml +2 -0
  71. data/config/locales/administrate.nl.yml +7 -5
  72. data/config/locales/administrate.pl.yml +2 -0
  73. data/config/locales/administrate.pt-BR.yml +4 -2
  74. data/config/locales/administrate.pt.yml +4 -2
  75. data/config/locales/administrate.ru.yml +2 -0
  76. data/config/locales/administrate.sl.yml +30 -0
  77. data/config/locales/{administrate.al.yml → administrate.sq.yml} +3 -1
  78. data/config/locales/administrate.sv.yml +2 -0
  79. data/config/locales/administrate.tr.yml +30 -0
  80. data/config/locales/administrate.uk.yml +2 -0
  81. data/config/locales/administrate.vi.yml +2 -0
  82. data/config/locales/administrate.zh-CN.yml +2 -0
  83. data/config/locales/administrate.zh-TW.yml +2 -0
  84. data/docs/adding_controllers_without_related_model.md +52 -0
  85. data/docs/adding_custom_field_types.md +3 -1
  86. data/docs/authentication.md +3 -1
  87. data/docs/authorization.md +47 -22
  88. data/docs/customizing_attribute_partials.md +4 -1
  89. data/docs/customizing_controller_actions.md +67 -1
  90. data/docs/customizing_dashboards.md +171 -40
  91. data/docs/customizing_page_views.md +18 -4
  92. data/docs/extending_administrate.md +27 -0
  93. data/docs/getting_started.md +35 -11
  94. data/docs/guides/customising_search.md +149 -0
  95. data/docs/guides/hiding_dashboards_from_sidebar.md +21 -0
  96. data/docs/guides/scoping_has_many_relations.md +27 -0
  97. data/docs/guides.md +7 -0
  98. data/docs/rails_api.md +5 -3
  99. data/lib/administrate/base_dashboard.rb +73 -14
  100. data/lib/administrate/custom_dashboard.rb +15 -0
  101. data/lib/administrate/engine.rb +9 -2
  102. data/lib/administrate/field/associative.rb +61 -7
  103. data/lib/administrate/field/base.rb +39 -9
  104. data/lib/administrate/field/belongs_to.rb +22 -5
  105. data/lib/administrate/field/date.rb +20 -0
  106. data/lib/administrate/field/deferred.rb +26 -3
  107. data/lib/administrate/field/has_many.rb +35 -8
  108. data/lib/administrate/field/has_one.rb +36 -12
  109. data/lib/administrate/field/number.rb +13 -2
  110. data/lib/administrate/field/polymorphic.rb +7 -6
  111. data/lib/administrate/field/select.rb +23 -4
  112. data/lib/administrate/field/time.rb +11 -0
  113. data/lib/administrate/field/url.rb +4 -0
  114. data/lib/administrate/namespace.rb +5 -1
  115. data/lib/administrate/not_authorized_error.rb +20 -0
  116. data/lib/administrate/order.rb +85 -19
  117. data/lib/administrate/page/base.rb +5 -3
  118. data/lib/administrate/page/collection.rb +1 -0
  119. data/lib/administrate/page/form.rb +12 -4
  120. data/lib/administrate/page/show.rb +10 -2
  121. data/lib/administrate/resource_resolver.rb +4 -3
  122. data/lib/administrate/search.rb +47 -36
  123. data/lib/administrate/version.rb +1 -1
  124. data/lib/administrate/view_generator.rb +14 -3
  125. data/lib/administrate.rb +42 -0
  126. data/lib/generators/administrate/dashboard/dashboard_generator.rb +38 -16
  127. data/lib/generators/administrate/dashboard/templates/controller.rb.erb +23 -11
  128. data/lib/generators/administrate/dashboard/templates/dashboard.rb.erb +4 -4
  129. data/lib/generators/administrate/install/install_generator.rb +43 -2
  130. data/lib/generators/administrate/install/templates/application_controller.rb.erb +4 -4
  131. data/lib/generators/administrate/routes/routes_generator.rb +26 -27
  132. data/lib/generators/administrate/test_record.rb +21 -0
  133. data/lib/generators/administrate/views/views_generator.rb +5 -4
  134. metadata +57 -78
  135. data/app/assets/javascripts/administrate/components/date_time_picker.js +0 -10
  136. data/app/assets/javascripts/administrate/components/has_many_form.js +0 -3
  137. data/config/i18n-tasks.yml +0 -18
  138. data/config/routes.rb +0 -2
  139. data/config/unicorn.rb +0 -30
@@ -1,16 +1,18 @@
1
- # Getting Started
1
+ ---
2
+ title: Getting Started
3
+ ---
2
4
 
3
5
  Administrate is released as a Ruby gem, and can be installed on Rails
4
- applications version 4.2 or greater.
6
+ applications version 6.0 or greater. We support Ruby 3.0 and up.
5
7
 
6
- Add the following to your Gemfile:
8
+ First, add the following to your Gemfile:
7
9
 
8
10
  ```ruby
9
11
  # Gemfile
10
12
  gem "administrate"
11
13
  ```
12
14
 
13
- Re-bundle, then run the installer:
15
+ Re-bundle with `bundle install`, then run the installer:
14
16
 
15
17
  ```bash
16
18
  $ rails generate administrate:install
@@ -35,7 +37,7 @@ You will also want to add a `root` route to show a dashboard when you go to `/ad
35
37
  Rails.application.routes.draw do
36
38
  namespace :admin do
37
39
  # Add dashboard for your models here
38
- resources :customers,
40
+ resources :customers
39
41
  resources :orders
40
42
 
41
43
  root to: "customers#index" # <--- Root route
@@ -54,6 +56,28 @@ Each `Admin::FooController` can be overwritten to specify custom behavior.
54
56
  Once you have Administrate installed,
55
57
  visit <http://localhost:3000/admin> to see your new dashboard in action.
56
58
 
59
+ ### Errors about assets?
60
+
61
+ If your apps uses Sprockets 4, you'll need to add Administrate's assets to
62
+ your `manifest.js` file. To do this, add these two lines to the file:
63
+
64
+ ```
65
+ //= link administrate/application.css
66
+ //= link administrate/application.js
67
+ ```
68
+
69
+ Otherwise, your app will show you this error:
70
+
71
+ ```
72
+ Asset `administrate/application.css` was not declared to be precompiled in production.
73
+ Declare links to your assets in `app/assets/config/manifest.js`.
74
+ ```
75
+
76
+ For more information on why this is necessary, see Richard Schneeman's article
77
+ ["Self Hosted Config: Introducing the Sprockets manifest.js"][]
78
+
79
+ [schneems]: https://www.schneems.com/2017/11/22/self-hosted-config-introducing-the-sprockets-manifestjs
80
+
57
81
  ## Create Additional Dashboards
58
82
 
59
83
  In order to create additional dashboards, pass in the resource name to
@@ -63,7 +87,7 @@ the dashboard generator. A dashboard and controller will be created.
63
87
  $ rails generate administrate:dashboard Foo
64
88
  ```
65
89
 
66
- Add a route for the new dashboard.
90
+ Then add a route for the new dashboard.
67
91
 
68
92
  ```ruby
69
93
  # config/routes.rb
@@ -84,8 +108,8 @@ rails generate administrate:install --namespace=supervisor
84
108
 
85
109
  ## Keep Dashboards Updated as Model Attributes Change
86
110
 
87
- If you've installed Administrate and generated dashboards and _then_
88
- subsequently added attributes to your models you'll need to manually add
111
+ If you've installed Administrate and generated dashboards and _then_
112
+ subsequently added attributes to your models you'll need to manually add
89
113
  these additions (or removals) to your dashboards.
90
114
 
91
115
  Example:
@@ -98,7 +122,7 @@ Example:
98
122
  the_new_attribute: Field::String,
99
123
  # ...
100
124
  }.freeze
101
-
125
+
102
126
  SHOW_PAGE_ATTRIBUTES = [
103
127
  # ...
104
128
  :the_new_attribute,
@@ -110,7 +134,7 @@ Example:
110
134
  :the_new_attribute,
111
135
  # ...
112
136
  ].freeze
113
-
137
+
114
138
  COLLECTION_ATTRIBUTES = [
115
139
  # ...
116
140
  :the_new_attribute, # if you want it on the index, also.
@@ -118,7 +142,7 @@ Example:
118
142
  ].freeze
119
143
  ```
120
144
 
121
- It's recommended that you make this change at the same time as you add the
145
+ It's recommended that you make this change at the same time as you add the
122
146
  attribute to the model.
123
147
 
124
148
  The alternative way to handle this is to re-run `rails g administrate:install`
@@ -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](https://administrate-demo.herokuapp.com/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
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: Hiding Dashboards from the Sidebar
3
+ ---
4
+
5
+ Resources can be removed from the sidebar by removing their `index` action
6
+ from the routes. For example:
7
+
8
+ ```ruby
9
+ # config/routes.rb
10
+ Rails.application.routes.draw do
11
+ namespace :admin do
12
+ resources :line_items, except: :index
13
+ resources :orders
14
+ resources :products
15
+ root to: "customers#index"
16
+ end
17
+ end
18
+ ```
19
+
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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: Guides
3
+ ---
4
+
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)
data/docs/rails_api.md CHANGED
@@ -1,7 +1,9 @@
1
- ## Rails API
1
+ ---
2
+ title: Rails API
3
+ ---
2
4
 
3
5
  Since Rails 5.0, we've been able to have API only applications. Yet, sometimes
4
- we still want to have an admin.
6
+ we still want to have an admin.
5
7
 
6
8
  To get this working, we recommend updating this config:
7
9
 
@@ -29,7 +31,7 @@ config.middleware.use ::Rack::MethodOverride
29
31
  ```
30
32
 
31
33
  You must also ensure that all the required controller actions are available
32
- and accessible as routes since generators in API-only applications only
34
+ and accessible as routes since generators in API-only applications only
33
35
  generate some of the required actions. Here is an example:
34
36
 
35
37
  ```ruby
@@ -1,6 +1,7 @@
1
1
  require "administrate/field/belongs_to"
2
2
  require "administrate/field/boolean"
3
3
  require "administrate/field/date_time"
4
+ require "administrate/field/date"
4
5
  require "administrate/field/email"
5
6
  require "administrate/field/has_many"
6
7
  require "administrate/field/has_one"
@@ -17,6 +18,18 @@ module Administrate
17
18
  class BaseDashboard
18
19
  include Administrate
19
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
+
20
33
  def attribute_types
21
34
  self.class::ATTRIBUTE_TYPES
22
35
  end
@@ -37,13 +50,37 @@ module Administrate
37
50
  attribute_types.keys
38
51
  end
39
52
 
40
- def form_attributes
41
- self.class::FORM_ATTRIBUTES
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
60
+ specific_form_attributes_for(action) || self.class::FORM_ATTRIBUTES
42
61
  end
43
62
 
44
- def permitted_attributes
45
- form_attributes.map do |attr|
46
- attribute_types[attr].permitted_attribute(attr)
63
+ def specific_form_attributes_for(action)
64
+ return unless action
65
+
66
+ cname = "FORM_ATTRIBUTES_#{action.upcase}"
67
+
68
+ self.class.const_get(cname) if self.class.const_defined?(cname)
69
+ end
70
+
71
+ def permitted_attributes(action = nil)
72
+ attributes = form_attributes action
73
+
74
+ if attributes.is_a? Hash
75
+ attributes = attributes.values.flatten
76
+ end
77
+
78
+ attributes.map do |attr|
79
+ attribute_types[attr].permitted_attribute(
80
+ attr,
81
+ resource_class: self.class.model,
82
+ action: action,
83
+ )
47
84
  end.uniq
48
85
  end
49
86
 
@@ -52,7 +89,17 @@ module Administrate
52
89
  end
53
90
 
54
91
  def collection_attributes
55
- self.class::COLLECTION_ATTRIBUTES
92
+ if self.class::COLLECTION_ATTRIBUTES.is_a?(Hash)
93
+ self.class::COLLECTION_ATTRIBUTES.values.flatten
94
+ else
95
+ self.class::COLLECTION_ATTRIBUTES
96
+ end
97
+ end
98
+
99
+ def search_attributes
100
+ attribute_types.keys.select do |attribute|
101
+ attribute_types[attribute].searchable?
102
+ end
56
103
  end
57
104
 
58
105
  def display_resource(resource)
@@ -64,27 +111,39 @@ module Administrate
64
111
  end
65
112
 
66
113
  def item_includes
114
+ # Deprecated, internal usage has moved to #item_associations
115
+ Administrate.warn_of_deprecated_method(self.class, :item_includes)
67
116
  attribute_includes(show_page_attributes)
68
117
  end
69
118
 
119
+ def item_associations
120
+ attributes = if show_page_attributes.is_a?(Hash)
121
+ show_page_attributes.values.flatten
122
+ else
123
+ show_page_attributes
124
+ end
125
+ attribute_associated attributes
126
+ end
127
+
70
128
  private
71
129
 
72
130
  def attribute_not_found_message(attr)
73
131
  "Attribute #{attr} could not be found in #{self.class}::ATTRIBUTE_TYPES"
74
132
  end
75
133
 
76
- def association_classes
77
- @association_classes ||=
78
- ObjectSpace.each_object(Class).
79
- select { |klass| klass < Administrate::Field::Associative }
134
+ def attribute_includes(attributes)
135
+ attributes.map do |key|
136
+ field = attribute_type_for(key)
137
+
138
+ key if field.eager_load?
139
+ end.compact
80
140
  end
81
141
 
82
- def attribute_includes(attributes)
142
+ def attribute_associated(attributes)
83
143
  attributes.map do |key|
84
- field = self.class::ATTRIBUTE_TYPES[key]
144
+ field = attribute_type_for(key)
85
145
 
86
- next key if association_classes.include?(field)
87
- key if association_classes.include?(field.try(:deferred_class))
146
+ key if field.associative?
88
147
  end.compact
89
148
  end
90
149
  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
@@ -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"
@@ -22,6 +22,13 @@ module Administrate
22
22
  @@javascripts = []
23
23
  @@stylesheets = []
24
24
 
25
+ initializer "administrate.assets.precompile" do |app|
26
+ app.config.assets.precompile += [
27
+ "administrate/application.js",
28
+ "administrate/application.css",
29
+ ]
30
+ end
31
+
25
32
  def self.add_javascript(script)
26
33
  @@javascripts << script
27
34
  end
@@ -3,30 +3,84 @@ require_relative "base"
3
3
  module Administrate
4
4
  module Field
5
5
  class Associative < Base
6
+ def self.foreign_key_for(resource_class, attr)
7
+ reflection(resource_class, attr).foreign_key
8
+ end
9
+
10
+ def self.association_primary_key_for(resource_class, attr)
11
+ reflection(resource_class, attr).association_primary_key
12
+ end
13
+
14
+ def self.associated_class(resource_class, attr)
15
+ reflection(resource_class, attr).klass
16
+ end
17
+
18
+ def self.associated_class_name(resource_class, attr)
19
+ associated_class(resource_class, attr).name
20
+ end
21
+
22
+ def self.reflection(resource_class, attr)
23
+ resource_class.reflect_on_association(attr)
24
+ end
25
+
6
26
  def display_associated_resource
7
27
  associated_dashboard.display_resource(data)
8
28
  end
9
29
 
10
30
  def associated_class
11
- associated_class_name.constantize
31
+ if option_given?(:class_name)
32
+ associated_class_name.constantize
33
+ else
34
+ self.class.associated_class(resource.class, attribute)
35
+ end
12
36
  end
13
37
 
14
- protected
38
+ def associated_class_name
39
+ if option_given?(:class_name)
40
+ deprecated_option(:class_name)
41
+ else
42
+ self.class.associated_class_name(
43
+ resource.class,
44
+ attribute,
45
+ )
46
+ end
47
+ end
48
+
49
+ private
15
50
 
16
51
  def associated_dashboard
17
52
  "#{associated_class_name}Dashboard".constantize.new
18
53
  end
19
54
 
20
- def associated_class_name
21
- options.fetch(:class_name, attribute.to_s.singularize.camelcase)
55
+ def primary_key
56
+ # Deprecated, renamed `association_primary_key`
57
+ Administrate.warn_of_deprecated_method(self.class, :primary_key)
58
+ association_primary_key
22
59
  end
23
60
 
24
- def primary_key
25
- options.fetch(:primary_key, :id)
61
+ def association_primary_key
62
+ if option_given?(:primary_key)
63
+ deprecated_option(:primary_key)
64
+ else
65
+ self.class.association_primary_key_for(resource.class, attribute)
66
+ end
26
67
  end
27
68
 
28
69
  def foreign_key
29
- options.fetch(:foreign_key, :"#{attribute}_id")
70
+ if option_given?(:foreign_key)
71
+ deprecated_option(:foreign_key)
72
+ else
73
+ self.class.foreign_key_for(resource.class, attribute)
74
+ end
75
+ end
76
+
77
+ def option_given?(name)
78
+ options.key?(name)
79
+ end
80
+
81
+ def deprecated_option(name)
82
+ Administrate.warn_of_deprecated_option(name)
83
+ options.fetch(name)
30
84
  end
31
85
  end
32
86
  end
@@ -12,10 +12,26 @@ module Administrate
12
12
  field_type.dasherize
13
13
  end
14
14
 
15
+ def self.associative?
16
+ self < Associative
17
+ end
18
+
19
+ def self.eager_load?
20
+ false
21
+ end
22
+
15
23
  def self.searchable?
16
24
  false
17
25
  end
18
26
 
27
+ def self.field_type
28
+ to_s.split("::").last.underscore
29
+ end
30
+
31
+ def self.permitted_attribute(attr, _options = nil)
32
+ attr
33
+ end
34
+
19
35
  def initialize(attribute, data, page, options = {})
20
36
  @attribute = attribute
21
37
  @data = data
@@ -24,10 +40,6 @@ module Administrate
24
40
  @options = options
25
41
  end
26
42
 
27
- def self.permitted_attribute(attr, _options = nil)
28
- attr
29
- end
30
-
31
43
  def html_class
32
44
  self.class.html_class
33
45
  end
@@ -40,15 +52,33 @@ module Administrate
40
52
  "/fields/#{self.class.field_type}/#{page}"
41
53
  end
42
54
 
43
- attr_reader :attribute, :data, :page, :resource
55
+ def required?
56
+ return false unless resource.class.respond_to?(:validators_on)
44
57
 
45
- protected
58
+ resource.class.validators_on(attribute).any? do |v|
59
+ next false unless v.class == ActiveRecord::Validations::PresenceValidator
46
60
 
47
- attr_reader :options
61
+ options = v.options
62
+ next false if options.include?(:if)
63
+ next false if options.include?(:unless)
48
64
 
49
- def self.field_type
50
- to_s.split("::").last.underscore
65
+ if on_option = options[:on]
66
+ if on_option == :create && !resource.persisted?
67
+ next true
68
+ end
69
+
70
+ if on_option == :update && resource.persisted?
71
+ next true
72
+ end
73
+
74
+ next false
75
+ end
76
+
77
+ true
78
+ end
51
79
  end
80
+
81
+ attr_reader :attribute, :data, :options, :page, :resource
52
82
  end
53
83
  end
54
84
  end