administrate 0.13.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/administrate/components/associative.js +5 -0
  3. data/app/assets/stylesheets/administrate/base/_tables.scss +3 -0
  4. data/app/assets/stylesheets/administrate/components/_attributes.scss +3 -2
  5. data/app/assets/stylesheets/administrate/components/_buttons.scss +3 -0
  6. data/app/assets/stylesheets/administrate/components/_field-unit.scss +4 -0
  7. data/app/assets/stylesheets/administrate/components/_flashes.scss +0 -8
  8. data/app/assets/stylesheets/administrate/components/_main-content.scss +1 -0
  9. data/app/assets/stylesheets/administrate/library/_variables.scss +10 -8
  10. data/app/controllers/administrate/application_controller.rb +47 -13
  11. data/app/helpers/administrate/application_helper.rb +27 -28
  12. data/app/views/administrate/application/_collection.html.erb +24 -26
  13. data/app/views/administrate/application/_collection_header_actions.html.erb +4 -0
  14. data/app/views/administrate/application/_collection_item_actions.html.erb +17 -0
  15. data/app/views/administrate/application/_flashes.html.erb +1 -0
  16. data/app/views/administrate/application/_form.html.erb +2 -2
  17. data/app/views/administrate/application/_icons.html.erb +1 -1
  18. data/app/views/administrate/application/_index_header.html.erb +28 -0
  19. data/app/views/administrate/application/_navigation.html.erb +4 -4
  20. data/app/views/administrate/application/index.html.erb +9 -29
  21. data/app/views/administrate/application/show.html.erb +1 -1
  22. data/app/views/fields/belongs_to/_form.html.erb +3 -3
  23. data/app/views/fields/belongs_to/_index.html.erb +1 -1
  24. data/app/views/fields/belongs_to/_show.html.erb +1 -1
  25. data/app/views/fields/has_one/_index.html.erb +1 -1
  26. data/app/views/fields/has_one/_show.html.erb +4 -4
  27. data/app/views/fields/number/_form.html.erb +1 -1
  28. data/app/views/fields/polymorphic/_show.html.erb +1 -1
  29. data/app/views/fields/select/_form.html.erb +23 -9
  30. data/app/views/fields/time/_form.html.erb +3 -2
  31. data/app/views/fields/time/_index.html.erb +3 -1
  32. data/app/views/fields/time/_show.html.erb +3 -1
  33. data/app/views/fields/url/_index.html.erb +1 -1
  34. data/app/views/fields/url/_show.html.erb +1 -1
  35. data/app/views/layouts/administrate/application.html.erb +1 -0
  36. data/config/locales/administrate.ar.yml +2 -0
  37. data/config/locales/administrate.bs.yml +2 -0
  38. data/config/locales/administrate.ca.yml +2 -0
  39. data/config/locales/administrate.da.yml +2 -0
  40. data/config/locales/administrate.de.yml +2 -0
  41. data/config/locales/administrate.en.yml +2 -0
  42. data/config/locales/administrate.es.yml +2 -0
  43. data/config/locales/administrate.fi.yml +30 -0
  44. data/config/locales/administrate.fr.yml +4 -2
  45. data/config/locales/administrate.id.yml +2 -0
  46. data/config/locales/administrate.it.yml +2 -0
  47. data/config/locales/administrate.ja.yml +2 -0
  48. data/config/locales/administrate.ko.yml +2 -0
  49. data/config/locales/administrate.nl.yml +7 -5
  50. data/config/locales/administrate.pl.yml +2 -0
  51. data/config/locales/administrate.pt-BR.yml +4 -2
  52. data/config/locales/administrate.pt.yml +4 -2
  53. data/config/locales/administrate.ru.yml +2 -0
  54. data/config/locales/{administrate.al.yml → administrate.sq.yml} +3 -1
  55. data/config/locales/administrate.sv.yml +2 -0
  56. data/config/locales/administrate.tr.yml +30 -0
  57. data/config/locales/administrate.uk.yml +2 -0
  58. data/config/locales/administrate.vi.yml +2 -0
  59. data/config/locales/administrate.zh-CN.yml +2 -0
  60. data/config/locales/administrate.zh-TW.yml +2 -0
  61. data/config/unicorn.rb +8 -13
  62. data/docs/adding_controllers_without_related_model.md +52 -0
  63. data/docs/customizing_controller_actions.md +32 -0
  64. data/docs/customizing_dashboards.md +81 -23
  65. data/docs/customizing_page_views.md +15 -3
  66. data/docs/extending_administrate.md +27 -0
  67. data/docs/getting_started.md +27 -5
  68. data/docs/guides/hiding_dashboards_from_sidebar.md +19 -0
  69. data/docs/guides.md +5 -0
  70. data/lib/administrate/base_dashboard.rb +34 -12
  71. data/lib/administrate/custom_dashboard.rb +15 -0
  72. data/lib/administrate/engine.rb +7 -0
  73. data/lib/administrate/field/associative.rb +49 -5
  74. data/lib/administrate/field/base.rb +30 -0
  75. data/lib/administrate/field/belongs_to.rb +13 -3
  76. data/lib/administrate/field/deferred.rb +22 -3
  77. data/lib/administrate/field/has_many.rb +15 -2
  78. data/lib/administrate/field/has_one.rb +28 -8
  79. data/lib/administrate/field/number.rb +19 -2
  80. data/lib/administrate/field/polymorphic.rb +2 -2
  81. data/lib/administrate/field/select.rb +10 -1
  82. data/lib/administrate/field/time.rb +11 -0
  83. data/lib/administrate/namespace.rb +5 -1
  84. data/lib/administrate/order.rb +3 -1
  85. data/lib/administrate/page/base.rb +1 -1
  86. data/lib/administrate/page/form.rb +10 -3
  87. data/lib/administrate/resource_resolver.rb +2 -2
  88. data/lib/administrate/search.rb +47 -36
  89. data/lib/administrate/version.rb +1 -1
  90. data/lib/administrate/view_generator.rb +7 -1
  91. data/lib/administrate.rb +19 -0
  92. data/lib/generators/administrate/dashboard/dashboard_generator.rb +18 -14
  93. data/lib/generators/administrate/dashboard/templates/controller.rb.erb +2 -2
  94. data/lib/generators/administrate/dashboard/templates/dashboard.rb.erb +3 -3
  95. data/lib/generators/administrate/install/install_generator.rb +37 -1
  96. data/lib/generators/administrate/install/templates/application_controller.rb.erb +1 -1
  97. data/lib/generators/administrate/routes/routes_generator.rb +3 -13
  98. data/lib/generators/administrate/views/views_generator.rb +5 -4
  99. metadata +25 -44
  100. data/app/assets/javascripts/administrate/components/has_many_form.js +0 -3
  101. data/docs/contributing.md +0 -1
@@ -8,9 +8,9 @@ edit the dashboard file generated by the installation generator.
8
8
  By default, the file will look something like this:
9
9
 
10
10
  ```ruby
11
- require "administrate/dashboard/base"
11
+ require "administrate/base_dashboard"
12
12
 
13
- class CustomerDashboard < Administrate::Dashboard::Base
13
+ class CustomerDashboard < Administrate::BaseDashboard
14
14
  ATTRIBUTE_TYPES = {
15
15
  id: Field::Number,
16
16
  name: Field::String,
@@ -65,6 +65,7 @@ specify, including:
65
65
  - `Field::Select`
66
66
  - `Field::String`
67
67
  - `Field::Text`
68
+ - `Field::Url`
68
69
  - `Field::Password`
69
70
 
70
71
  ## Customizing Fields
@@ -79,21 +80,16 @@ which are specified through the `.with_options` class method:
79
80
  `:order` - Specifies the order of the dropdown menu, can be ordered by more
80
81
  than one column. e.g.: `"name, email DESC"`.
81
82
 
82
- `:primary_key` - Specifies object's primary_key. Defaults to `:id`.
83
-
84
- `:foreign_key` - Specifies the name of the foreign key directly.
85
- Defaults to `:#{attribute}_id`.
86
-
87
83
  `:scope` - Specifies a custom scope inside a callable. Useful for preloading.
88
84
  Example: `.with_options(scope: -> { MyModel.includes(:rel).limit(5) })`
89
85
 
90
- `:class_name` - Specifies the name of the associated class.
91
- Defaults to `:#{attribute}.to_s.singularize.camelcase`.
86
+ `:include_blank` - Specifies if the select element to be rendered should include
87
+ blank option. Default is `true`.
92
88
 
93
89
  `:searchable` - Specify if the attribute should be considered when searching.
94
90
  Default is `false`.
95
91
 
96
- `searchable_field` - Specify which column to use on the search, only applies
92
+ `searchable_fields` - Specify which columns to use on the search, only applies
97
93
  if `searchable` is `true`
98
94
 
99
95
  For example:
@@ -101,13 +97,19 @@ For example:
101
97
  ```ruby
102
98
  country: Field::BelongsTo.with_options(
103
99
  searchable: true,
104
- searchable_field: 'name',
100
+ searchable_fields: ['name'],
105
101
  )
106
102
  ```
107
103
 
108
104
  with this, you will be able to search through the column `name` from the
109
105
  association `belongs_to :country`, from your model.
110
106
 
107
+ `:primary_key` (deprecated) - Specifies the association's primary_key.
108
+
109
+ `:foreign_key` (deprecated) - Specifies the name of the foreign key directly.
110
+
111
+ `:class_name` (deprecated) - Specifies the name of the associated class.
112
+
111
113
  **Field::HasMany**
112
114
 
113
115
  `:limit` - Set the number of resources to display in the show view. Default is
@@ -117,22 +119,18 @@ association `belongs_to :country`, from your model.
117
119
 
118
120
  `:direction` - What direction the sort should be in, `:asc` (default) or `:desc`.
119
121
 
120
- `:primary_key` - Specifies object's primary_key. Defaults to `:id`.
122
+ `:primary_key` (deprecated) - Specifies object's primary_key.
121
123
 
122
- `:foreign_key` - Specifies the name of the foreign key directly. Defaults to `:#{attribute}_id`
124
+ `:foreign_key` (deprecated) - Specifies the name of the foreign key directly.
123
125
 
124
- `:class_name` - Specifies the name of the associated class.
125
- Defaults to `:#{attribute}.to_s.singularize.camelcase`.
126
+ `:class_name` (deprecated) - Specifies the name of the associated class.
126
127
 
127
128
  **Field::HasOne**
128
129
 
129
- `:class_name` - Specifies the name of the associated class.
130
- Defaults to `:#{attribute}.to_s.singularize.camelcase`.
131
-
132
130
  `:searchable` - Specify if the attribute should be considered when searching.
133
131
  Default is `false`.
134
132
 
135
- `searchable_field` - Specify which column to use on the search, only applies if
133
+ `searchable_fields` - Specify which columns to use on the search, only applies if
136
134
  `searchable` is `true`
137
135
 
138
136
  For example:
@@ -140,13 +138,15 @@ For example:
140
138
  ```ruby
141
139
  cities: Field::HasMany.with_options(
142
140
  searchable: true,
143
- searchable_field: 'name',
141
+ searchable_fields: ['name'],
144
142
  )
145
143
  ```
146
144
 
147
145
  with this, you will be able to search through the column `name` from the
148
146
  association `has_many :cities`, from your model.
149
147
 
148
+ `:class_name` (deprecated) - Specifies the name of the associated class.
149
+
150
150
  **Field::Number**
151
151
 
152
152
  `:searchable` - Specify if the attribute should be considered when searching.
@@ -159,6 +159,13 @@ more results than expected. Default is `false`.
159
159
 
160
160
  `:suffix` - Suffixes the number with a string. Defaults to `""`.
161
161
 
162
+ `:format` - Specify a hash which defines a formatter. This uses ActiveSupport
163
+ and works by by passing a hash that includes the formatter (`formatter`) and
164
+ the options for the formatter (`formatter_options`). Defaults to the locale's
165
+ delimiter when `formatter_options` does not include a `delimiter`. See the
166
+ example below. Note that currently only
167
+ `ActiveSupport::NumberHelper.number_to_delimited` is supported.
168
+
162
169
  For example, you might use the following to display U.S. currency:
163
170
 
164
171
  ```ruby
@@ -166,15 +173,25 @@ For example, you might use the following to display U.S. currency:
166
173
  prefix: "$",
167
174
  decimals: 2,
168
175
  )
176
+
177
+ # "$5.99"
169
178
  ```
170
179
 
171
- Or, to display a distance in kilometers:
180
+ Or, to display a distance in kilometers, using a space as the delimiter:
172
181
 
173
182
  ```ruby
174
- unit_price: Field::Number.with_options(
183
+ distance: Field::Number.with_options(
175
184
  suffix: " km",
176
185
  decimals: 2,
186
+ format: {
187
+ formatter: :number_to_delimited,
188
+ formatter_options: {
189
+ delimiter: ' ',
190
+ },
191
+ },
177
192
  )
193
+
194
+ # "2 000.00 km"
178
195
  ```
179
196
 
180
197
  **Field::Polymorphic**
@@ -200,11 +217,15 @@ objects to display as.
200
217
 
201
218
  **Field::Select**
202
219
 
203
- `:collection` - Specify the array or range to select from. Defaults to `[]`.
220
+ `:collection` - Specify the options shown on the select field. It accept either
221
+ an array or an object responding to `:call`. Defaults to `[]`.
204
222
 
205
223
  `:searchable` - Specify if the attribute should be considered when searching.
206
224
  Default is `true`.
207
225
 
226
+ `:include_blank` - Specifies if the select element to be rendered should include
227
+ blank option. Default is `false`.
228
+
208
229
  **Field::String**
209
230
 
210
231
  `:searchable` - Specify if the attribute should be considered when searching.
@@ -221,6 +242,14 @@ Default is `false`.
221
242
  `:truncate` - Set the number of characters to display in the index view.
222
243
  Defaults to `50`.
223
244
 
245
+ **Field::Url**
246
+
247
+ `:searchable` - Specify if the attribute should be considered when searching.
248
+ Default is `true`.
249
+
250
+ `:truncate` - Set the number of characters to display in the index view.
251
+ Defaults to `50`.
252
+
224
253
  **Field::Password**
225
254
 
226
255
  `:searchable` - Specify if the attribute should be considered when searching.
@@ -300,3 +329,32 @@ COLLECTION_FILTERS = {
300
329
  inactive: ->(resources) { resources.inactive }
301
330
  }
302
331
  ```
332
+
333
+ You can also define a filter with parameters:
334
+
335
+ ```ruby
336
+ COLLECTION_FILTERS = {
337
+ state: ->(resources, attr) { resources.where(state: attr) }
338
+ }
339
+ ```
340
+
341
+ You can now search your resource with 'state:open' and your
342
+ collection filter Proc will be called with with attr = open.
343
+
344
+ ## Form Attributes
345
+
346
+ You can define different attributes for new/create or edit/update actions:
347
+
348
+ ```ruby
349
+ FORM_ATTRIBUTES_NEW = [
350
+ :name,
351
+ :email
352
+ ]
353
+ FORM_ATTRIBUTES_EDIT = [
354
+ :name,
355
+ :email,
356
+ :orders
357
+ ]
358
+ ```
359
+
360
+ Or for custom action with constant name `"FORM_ATTRIBUTES_#{action.upcase}"`
@@ -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
 
@@ -0,0 +1,27 @@
1
+ ---
2
+ title: Extending Administrate
3
+ ---
4
+
5
+ Apart from the configuration described in these pages, it is possible to
6
+ extend Administrate's capabilities with the use of plugins. There are a
7
+ number of plugins available, many of which can be found at [RubyGems.org].
8
+ These are some popular examples:
9
+
10
+ 1. [ActiveStorage support](https://github.com/Dreamersoul/administrate-field-active_storage)
11
+ 2. [Enum field](https://github.com/Valiot/administrate-field-enum)
12
+ 3. [Nested has-many forms](https://github.com/nickcharlton/administrate-field-nested_has_many)
13
+ 4. [Belongs-to with Ajax search](https://github.com/fishbrain/administrate-field-belongs_to_search)
14
+ 5. [JSONb field plugin for Administrate](https://github.com/codica2/administrate-field-jsonb/)
15
+
16
+ See many more at https://rubygems.org/gems/administrate/reverse_dependencies.
17
+
18
+ Please note that these plugins are written by third parties. We do not
19
+ have any control over them, and we cannot give any assurances as to how
20
+ well they perform their advertised functions.
21
+
22
+ You can write your own plugins too! We don't document this specifically,
23
+ but you can have a look at the existing plugins for some directions.
24
+ In general, Administrate tries to abide by Rails's conventions, so that
25
+ hopefully should help!
26
+
27
+ [RubyGems.org]: https://rubygems.org
@@ -3,16 +3,16 @@ 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 4.2 or greater.
6
+ applications version 5.0 or greater. We support Ruby 2.6 and up.
7
7
 
8
- Add the following to your Gemfile:
8
+ First, add the following to your Gemfile:
9
9
 
10
10
  ```ruby
11
11
  # Gemfile
12
12
  gem "administrate"
13
13
  ```
14
14
 
15
- Re-bundle, then run the installer:
15
+ Re-bundle with `bundle install`, then run the installer:
16
16
 
17
17
  ```bash
18
18
  $ rails generate administrate:install
@@ -37,7 +37,7 @@ You will also want to add a `root` route to show a dashboard when you go to `/ad
37
37
  Rails.application.routes.draw do
38
38
  namespace :admin do
39
39
  # Add dashboard for your models here
40
- resources :customers,
40
+ resources :customers
41
41
  resources :orders
42
42
 
43
43
  root to: "customers#index" # <--- Root route
@@ -56,6 +56,28 @@ Each `Admin::FooController` can be overwritten to specify custom behavior.
56
56
  Once you have Administrate installed,
57
57
  visit <http://localhost:3000/admin> to see your new dashboard in action.
58
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
+
59
81
  ## Create Additional Dashboards
60
82
 
61
83
  In order to create additional dashboards, pass in the resource name to
@@ -65,7 +87,7 @@ the dashboard generator. A dashboard and controller will be created.
65
87
  $ rails generate administrate:dashboard Foo
66
88
  ```
67
89
 
68
- Add a route for the new dashboard.
90
+ Then add a route for the new dashboard.
69
91
 
70
92
  ```ruby
71
93
  # config/routes.rb
@@ -0,0 +1,19 @@
1
+ ---
2
+ title: Hiding Dashboards from the Sidebar
3
+ ---
4
+
5
+ Resources can be removed form the sidebar by removing their index action from the routes. For example:
6
+
7
+ ```ruby
8
+ # config/routes.rb
9
+ Rails.application.routes.draw do
10
+ namespace :admin do
11
+ resources :line_items, except: :index
12
+ resources :orders
13
+ resources :products
14
+ root to: "customers#index"
15
+ end
16
+ end
17
+ ```
18
+
19
+ In this case, only Orders and Products will appear in the sidebar, while Line Items can still appear as an association.
data/docs/guides.md ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ title: Guides
3
+ ---
4
+
5
+ * [Hiding Dashboards from the Sidebar](./guides/hiding_dashboards_from_sidebar)
@@ -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
@@ -38,13 +50,24 @@ module Administrate
38
50
  attribute_types.keys
39
51
  end
40
52
 
41
- def form_attributes
42
- self.class::FORM_ATTRIBUTES
53
+ def form_attributes(action = nil)
54
+ specific_form_attributes_for(action) || self.class::FORM_ATTRIBUTES
55
+ end
56
+
57
+ def specific_form_attributes_for(action)
58
+ return unless action
59
+
60
+ cname = "FORM_ATTRIBUTES_#{action.upcase}"
61
+
62
+ self.class.const_get(cname) if self.class.const_defined?(cname)
43
63
  end
44
64
 
45
65
  def permitted_attributes
46
66
  form_attributes.map do |attr|
47
- attribute_types[attr].permitted_attribute(attr)
67
+ attribute_types[attr].permitted_attribute(
68
+ attr,
69
+ resource_class: self.class.model,
70
+ )
48
71
  end.uniq
49
72
  end
50
73
 
@@ -56,6 +79,12 @@ module Administrate
56
79
  self.class::COLLECTION_ATTRIBUTES
57
80
  end
58
81
 
82
+ def search_attributes
83
+ attribute_types.keys.select do |attribute|
84
+ attribute_types[attribute].searchable?
85
+ end
86
+ end
87
+
59
88
  def display_resource(resource)
60
89
  "#{resource.class} ##{resource.id}"
61
90
  end
@@ -74,18 +103,11 @@ module Administrate
74
103
  "Attribute #{attr} could not be found in #{self.class}::ATTRIBUTE_TYPES"
75
104
  end
76
105
 
77
- def association_classes
78
- @association_classes ||=
79
- ObjectSpace.each_object(Class).
80
- select { |klass| klass < Administrate::Field::Associative }
81
- end
82
-
83
106
  def attribute_includes(attributes)
84
107
  attributes.map do |key|
85
- field = self.class::ATTRIBUTE_TYPES[key]
108
+ field = attribute_type_for(key)
86
109
 
87
- next key if association_classes.include?(field)
88
- key if association_classes.include?(field.try(:deferred_class))
110
+ key if field.associative?
89
111
  end.compact
90
112
  end
91
113
  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
@@ -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,74 @@ 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.associated_class(resource_class, attr)
11
+ reflection(resource_class, attr).klass
12
+ end
13
+
14
+ def self.associated_class_name(resource_class, attr)
15
+ reflection(resource_class, attr).class_name
16
+ end
17
+
18
+ def self.reflection(resource_class, attr)
19
+ resource_class.reflect_on_association(attr)
20
+ end
21
+
6
22
  def display_associated_resource
7
23
  associated_dashboard.display_resource(data)
8
24
  end
9
25
 
10
26
  def associated_class
11
- associated_class_name.constantize
27
+ if option_given?(:class_name)
28
+ associated_class_name.constantize
29
+ else
30
+ self.class.associated_class(resource.class, attribute)
31
+ end
12
32
  end
13
33
 
14
- protected
34
+ private
15
35
 
16
36
  def associated_dashboard
17
37
  "#{associated_class_name}Dashboard".constantize.new
18
38
  end
19
39
 
20
40
  def associated_class_name
21
- options.fetch(:class_name, attribute.to_s.singularize.camelcase)
41
+ if option_given?(:class_name)
42
+ deprecated_option(:class_name)
43
+ else
44
+ self.class.associated_class_name(
45
+ resource.class,
46
+ attribute,
47
+ )
48
+ end
22
49
  end
23
50
 
24
51
  def primary_key
25
- options.fetch(:primary_key, :id)
52
+ if option_given?(:primary_key)
53
+ deprecated_option(:primary_key)
54
+ else
55
+ :id
56
+ end
26
57
  end
27
58
 
28
59
  def foreign_key
29
- options.fetch(:foreign_key, :"#{attribute}_id")
60
+ if option_given?(:foreign_key)
61
+ deprecated_option(:foreign_key)
62
+ else
63
+ self.class.foreign_key_for(resource.class, attribute)
64
+ end
65
+ end
66
+
67
+ def option_given?(name)
68
+ options.key?(name)
69
+ end
70
+
71
+ def deprecated_option(name)
72
+ Administrate.warn_of_deprecated_option(name)
73
+ options.fetch(name)
30
74
  end
31
75
  end
32
76
  end
@@ -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
@@ -44,6 +48,32 @@ module Administrate
44
48
  "/fields/#{self.class.field_type}/#{page}"
45
49
  end
46
50
 
51
+ def required?
52
+ return false unless resource.class.respond_to?(:validators_on)
53
+
54
+ resource.class.validators_on(attribute).any? do |v|
55
+ next false unless v.class == ActiveRecord::Validations::PresenceValidator
56
+
57
+ options = v.options
58
+ next false if options.include?(:if)
59
+ next false if options.include?(:unless)
60
+
61
+ if on_option = options[:on]
62
+ if on_option == :create && !resource.persisted?
63
+ next true
64
+ end
65
+
66
+ if on_option == :update && resource.persisted?
67
+ next true
68
+ end
69
+
70
+ next false
71
+ end
72
+
73
+ true
74
+ end
75
+ end
76
+
47
77
  attr_reader :attribute, :data, :options, :page, :resource
48
78
  end
49
79
  end
@@ -3,8 +3,14 @@ require_relative "associative"
3
3
  module Administrate
4
4
  module Field
5
5
  class BelongsTo < Associative
6
- def self.permitted_attribute(attr, _options = nil)
7
- :"#{attr}_id"
6
+ def self.permitted_attribute(attr, options = {})
7
+ resource_class = options[:resource_class]
8
+ if resource_class
9
+ foreign_key_for(resource_class, attr)
10
+ else
11
+ Administrate.warn_of_missing_resource_class
12
+ :"#{attr}_id"
13
+ end
8
14
  end
9
15
 
10
16
  def permitted_attribute
@@ -12,7 +18,7 @@ module Administrate
12
18
  end
13
19
 
14
20
  def associated_resource_options
15
- [nil] + candidate_resources.map do |resource|
21
+ candidate_resources.map do |resource|
16
22
  [display_candidate_resource(resource), resource.send(primary_key)]
17
23
  end
18
24
  end
@@ -21,6 +27,10 @@ module Administrate
21
27
  data && data.send(primary_key)
22
28
  end
23
29
 
30
+ def include_blank_option
31
+ options.fetch(:include_blank, true)
32
+ end
33
+
24
34
  private
25
35
 
26
36
  def candidate_resources
@@ -21,17 +21,36 @@ 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
 
32
- def permitted_attribute(attr, _options = nil)
33
- options.fetch(:foreign_key,
34
- deferred_class.permitted_attribute(attr, options))
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
+
47
+ def permitted_attribute(attr, opts = {})
48
+ if options.key?(:foreign_key)
49
+ Administrate.warn_of_deprecated_option(:foreign_key)
50
+ options.fetch(:foreign_key)
51
+ else
52
+ deferred_class.permitted_attribute(attr, options.merge(opts))
53
+ end
35
54
  end
36
55
 
37
56
  delegate :html_class, to: :deferred_class