goodmin 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +1359 -0
  4. data/Rakefile +56 -0
  5. data/app/assets/stylesheets/goodmin/application.css +75 -0
  6. data/app/controllers/goodmin/application_controller.rb +43 -0
  7. data/app/controllers/goodmin/resource_controller.rb +235 -0
  8. data/app/helpers/goodmin/application_helper.rb +45 -0
  9. data/app/javascript/goodmin/application.js +8 -0
  10. data/app/javascript/goodmin/controllers/batch_actions_controller.js +101 -0
  11. data/app/javascript/goodmin/controllers/datetimepicker_controller.js +24 -0
  12. data/app/javascript/goodmin/controllers/navigation_controller.js +30 -0
  13. data/app/jobs/goodmin/application_job.rb +4 -0
  14. data/app/mailers/goodmin/application_mailer.rb +6 -0
  15. data/app/models/goodmin/application_record.rb +5 -0
  16. data/app/views/goodmin/application/welcome.html.erb +17 -0
  17. data/app/views/goodmin/fields/association/_form.html.erb +19 -0
  18. data/app/views/goodmin/fields/association/_index.html.erb +6 -0
  19. data/app/views/goodmin/fields/association/_show.html.erb +6 -0
  20. data/app/views/goodmin/fields/boolean/_form.html.erb +7 -0
  21. data/app/views/goodmin/fields/boolean/_index.html.erb +1 -0
  22. data/app/views/goodmin/fields/boolean/_show.html.erb +1 -0
  23. data/app/views/goodmin/fields/date/_form.html.erb +6 -0
  24. data/app/views/goodmin/fields/date/_index.html.erb +1 -0
  25. data/app/views/goodmin/fields/date/_show.html.erb +1 -0
  26. data/app/views/goodmin/fields/date_time/_form.html.erb +6 -0
  27. data/app/views/goodmin/fields/date_time/_index.html.erb +1 -0
  28. data/app/views/goodmin/fields/date_time/_show.html.erb +1 -0
  29. data/app/views/goodmin/fields/enum/_index.html.erb +1 -0
  30. data/app/views/goodmin/fields/enum/_show.html.erb +1 -0
  31. data/app/views/goodmin/fields/nested_has_one/_form.html.erb +6 -0
  32. data/app/views/goodmin/fields/nested_has_one/_index.html.erb +1 -0
  33. data/app/views/goodmin/fields/nested_has_one/_show.html.erb +1 -0
  34. data/app/views/goodmin/fields/number/_form.html.erb +5 -0
  35. data/app/views/goodmin/fields/number/_index.html.erb +1 -0
  36. data/app/views/goodmin/fields/number/_show.html.erb +1 -0
  37. data/app/views/goodmin/fields/password/_form.html.erb +5 -0
  38. data/app/views/goodmin/fields/password/_index.html.erb +1 -0
  39. data/app/views/goodmin/fields/password/_show.html.erb +1 -0
  40. data/app/views/goodmin/fields/select/_form.html.erb +5 -0
  41. data/app/views/goodmin/fields/select/_index.html.erb +1 -0
  42. data/app/views/goodmin/fields/select/_show.html.erb +1 -0
  43. data/app/views/goodmin/fields/string/_form.html.erb +5 -0
  44. data/app/views/goodmin/fields/string/_index.html.erb +1 -0
  45. data/app/views/goodmin/fields/string/_show.html.erb +1 -0
  46. data/app/views/goodmin/fields/text/_form.html.erb +5 -0
  47. data/app/views/goodmin/fields/text/_index.html.erb +1 -0
  48. data/app/views/goodmin/fields/text/_show.html.erb +1 -0
  49. data/app/views/goodmin/resource/_actions.html.erb +9 -0
  50. data/app/views/goodmin/resource/_batch_actions.html.erb +12 -0
  51. data/app/views/goodmin/resource/_breadcrumb.html.erb +33 -0
  52. data/app/views/goodmin/resource/_breadcrumb_actions.html.erb +41 -0
  53. data/app/views/goodmin/resource/_button_actions.html.erb +3 -0
  54. data/app/views/goodmin/resource/_errors.html.erb +9 -0
  55. data/app/views/goodmin/resource/_export_actions.html.erb +15 -0
  56. data/app/views/goodmin/resource/_filters.html.erb +22 -0
  57. data/app/views/goodmin/resource/_form.html.erb +26 -0
  58. data/app/views/goodmin/resource/_pagination.html.erb +40 -0
  59. data/app/views/goodmin/resource/_scopes.html.erb +14 -0
  60. data/app/views/goodmin/resource/_table.html.erb +45 -0
  61. data/app/views/goodmin/resource/columns/_actions.html.erb +28 -0
  62. data/app/views/goodmin/resource/edit.html.erb +5 -0
  63. data/app/views/goodmin/resource/index.csv.csvbuilder +5 -0
  64. data/app/views/goodmin/resource/index.html.erb +10 -0
  65. data/app/views/goodmin/resource/index.json.jbuilder +3 -0
  66. data/app/views/goodmin/resource/new.html.erb +5 -0
  67. data/app/views/goodmin/resource/show.html.erb +11 -0
  68. data/app/views/goodmin/resource/show.json.jbuilder +1 -0
  69. data/app/views/goodmin/sessions/new.html.erb +11 -0
  70. data/app/views/goodmin/shared/_navigation.html.erb +0 -0
  71. data/app/views/goodmin/shared/_navigation_aside.html.erb +7 -0
  72. data/app/views/layouts/goodmin/_content.html.erb +13 -0
  73. data/app/views/layouts/goodmin/_layout.html.erb +22 -0
  74. data/app/views/layouts/goodmin/application.html.erb +28 -0
  75. data/app/views/layouts/goodmin/login.html.erb +18 -0
  76. data/config/importmap.rb +5 -0
  77. data/config/locales/en.yml +49 -0
  78. data/config/locales/pl-BR.yml +49 -0
  79. data/config/locales/pt-BR.yml +49 -0
  80. data/config/locales/sv.yml +49 -0
  81. data/config/routes.rb +3 -0
  82. data/lib/generators/goodmin/authentication/authentication_generator.rb +41 -0
  83. data/lib/generators/goodmin/authentication/templates/sessions_controller.rb +9 -0
  84. data/lib/generators/goodmin/install/install_generator.rb +41 -0
  85. data/lib/generators/goodmin/policy/policy_generator.rb +7 -0
  86. data/lib/generators/goodmin/policy/templates/policy.rb +23 -0
  87. data/lib/generators/goodmin/resource/resource_generator.rb +31 -0
  88. data/lib/generators/goodmin/resource/templates/resource.rb +25 -0
  89. data/lib/generators/goodmin/resource/templates/resource_controller.rb +9 -0
  90. data/lib/generators/goodmin/resource/templates/resource_model.rb +4 -0
  91. data/lib/generators/goodmin/resource/templates/resource_service.rb +23 -0
  92. data/lib/goodmin/authentication/sessions_controller.rb +46 -0
  93. data/lib/goodmin/authentication/user.rb +27 -0
  94. data/lib/goodmin/authentication.rb +35 -0
  95. data/lib/goodmin/authorization/policy.rb +41 -0
  96. data/lib/goodmin/authorization.rb +69 -0
  97. data/lib/goodmin/engine.rb +30 -0
  98. data/lib/goodmin/fields/association.rb +62 -0
  99. data/lib/goodmin/fields/base.rb +57 -0
  100. data/lib/goodmin/fields/boolean.rb +6 -0
  101. data/lib/goodmin/fields/date.rb +6 -0
  102. data/lib/goodmin/fields/date_time.rb +6 -0
  103. data/lib/goodmin/fields/enum.rb +15 -0
  104. data/lib/goodmin/fields/nested_has_one.rb +41 -0
  105. data/lib/goodmin/fields/number.rb +6 -0
  106. data/lib/goodmin/fields/password.rb +6 -0
  107. data/lib/goodmin/fields/select.rb +19 -0
  108. data/lib/goodmin/fields/string.rb +6 -0
  109. data/lib/goodmin/fields/text.rb +6 -0
  110. data/lib/goodmin/generators/base.rb +49 -0
  111. data/lib/goodmin/generators/named_base.rb +31 -0
  112. data/lib/goodmin/helpers/application.rb +47 -0
  113. data/lib/goodmin/helpers/batch_actions.rb +21 -0
  114. data/lib/goodmin/helpers/filters.rb +123 -0
  115. data/lib/goodmin/helpers/forms.rb +42 -0
  116. data/lib/goodmin/helpers/navigation.rb +52 -0
  117. data/lib/goodmin/helpers/tables.rb +25 -0
  118. data/lib/goodmin/helpers/translations.rb +19 -0
  119. data/lib/goodmin/paginator.rb +55 -0
  120. data/lib/goodmin/resolver.rb +141 -0
  121. data/lib/goodmin/resources/attribute.rb +46 -0
  122. data/lib/goodmin/resources/form_builder.rb +96 -0
  123. data/lib/goodmin/resources/form_component.rb +65 -0
  124. data/lib/goodmin/resources/form_components/col.rb +33 -0
  125. data/lib/goodmin/resources/form_components/row.rb +25 -0
  126. data/lib/goodmin/resources/form_components/section.rb +51 -0
  127. data/lib/goodmin/resources/form_components/tab.rb +49 -0
  128. data/lib/goodmin/resources/resource/associations.rb +23 -0
  129. data/lib/goodmin/resources/resource/batch_actions.rb +56 -0
  130. data/lib/goodmin/resources/resource/filters.rb +44 -0
  131. data/lib/goodmin/resources/resource/ordering.rb +41 -0
  132. data/lib/goodmin/resources/resource/pagination.rb +22 -0
  133. data/lib/goodmin/resources/resource/scopes.rb +61 -0
  134. data/lib/goodmin/resources/resource.rb +199 -0
  135. data/lib/goodmin/resources/resource_controller/batch_actions.rb +49 -0
  136. data/lib/goodmin/resources/resource_service/associations.rb +23 -0
  137. data/lib/goodmin/resources/resource_service/batch_actions.rb +52 -0
  138. data/lib/goodmin/resources/resource_service/filters.rb +44 -0
  139. data/lib/goodmin/resources/resource_service/ordering.rb +41 -0
  140. data/lib/goodmin/resources/resource_service/pagination.rb +22 -0
  141. data/lib/goodmin/resources/resource_service/scopes.rb +61 -0
  142. data/lib/goodmin/resources/resource_service.rb +199 -0
  143. data/lib/goodmin/service_locator.rb +25 -0
  144. data/lib/goodmin/version.rb +3 -0
  145. data/lib/goodmin.rb +44 -0
  146. data/lib/tasks/goodmin_tasks.rake +4 -0
  147. metadata +461 -0
data/README.md ADDED
@@ -0,0 +1,1359 @@
1
+ # Goodmin
2
+
3
+ [![Gem Version](http://img.shields.io/gem/v/goodmin.svg)](https://rubygems.org/gems/goodmin)
4
+ [![Build Status](https://img.shields.io/travis/varvet/goodmin/master.svg)](https://travis-ci.org/varvet/goodmin)
5
+ [![Code Climate](https://api.codeclimate.com/v1/badges/d8e5c7c54c1dba073689/maintainability)](https://codeclimate.com/github/varvet/goodmin)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/d8e5c7c54c1dba073689/test_coverage)](https://codeclimate.com/github/varvet/goodmin)
7
+
8
+ Goodmin is an admin framework for Rails 5+. Use it to build dedicated admin sections for your apps, or stand alone admin apps such as internal tools. It has support for common features such as scoping, filtering and performing batch actions on your models. Check out the [demo app](http://goodmin-sandbox.herokuapp.com) and its [source code](https://github.com/varvet/goodmin-sandbox) to get a feel for how it works.
9
+
10
+ Goodmin differs from tools like [ActiveAdmin](http://activeadmin.info/) and [RailsAdmin](https://github.com/sferik/rails_admin) in how admin sections are created. Rather than being DSL-based, Goodmin is a set of opt-in modules and helpers that can be applied to regular Rails apps and engines. An admin section built with Goodmin is just that, a regular Rails app or Rails engine, with regular routes, controllers and views. That means there is less to learn, because you already know most of it, and fewer constraints on what you can do. After all, administrators are users too, and what better way to provide them with a tailor made experience than building them a Rails app?
11
+
12
+ ![Screenshot](https://raw.githubusercontent.com/varvet/goodmin/master/screenshot.png)
13
+
14
+ - [Installation](#installation)
15
+ - [Standalone installation](#standalone-installation)
16
+ - [Engine installation](#engine-installation)
17
+ - [Installation artefacts](#installation-artefacts)
18
+ - [Getting started](#getting-started)
19
+ - [Resources](#resources)
20
+ - [Scopes](#scopes)
21
+ - [Filters](#filters)
22
+ - [Batch actions](#batch-actions)
23
+ - [Custom ordering](#custom-ordering)
24
+ - [Resource fetching, building and saving](#resource-fetching-building-and-saving)
25
+ - [Redirecting](#redirecting)
26
+ - [Pagination](#pagination)
27
+ - [Exporting](#exporting)
28
+ - [Nested resources](#nested-resources)
29
+ - [Views](#views)
30
+ - [Forms](#forms)
31
+ - [Navigation](#navigation)
32
+ - [Authentication](#authentication)
33
+ - [Built in authentication](#built-in-authentication)
34
+ - [Shared authentication](#shared-authentication)
35
+ - [Authorization](#authorization)
36
+ - [Localization](#localization)
37
+ - [JavaScript](#javascript)
38
+ - [Plugins](#plugins)
39
+ - [Contributors](#contributors)
40
+ - [License](#license)
41
+
42
+ ## Installation
43
+
44
+ Goodmin supports two common admin scenarios:
45
+
46
+ 1. Standalone installation
47
+ 2. Engine installation
48
+
49
+ If you want to set up an example app that you can play around with, run the following:
50
+ ```sh
51
+ rails new sandbox --skip-spring -m https://raw.githubusercontent.com/varvet/goodmin/master/template.rb
52
+ ```
53
+
54
+ ### Standalone installation
55
+ Use for admin-only applications, or for architectures where the admin lives in its own app. E.g. you want to access the admin section at `localhost:3000`.
56
+
57
+ Add the gem to the application's `Gemfile`:
58
+ ```ruby
59
+ gem "goodmin"
60
+ ```
61
+
62
+ Bundle, then run the install generator:
63
+ ```sh
64
+ $ bundle install
65
+ $ bin/rails generate goodmin:install
66
+ ```
67
+
68
+ Goodmin should be up and running at `localhost:3000`.
69
+
70
+ ### Engine installation
71
+ Use when the admin is part of the same codebase as the main application. E.g. you want to access the admin section at `localhost:3000/admin`.
72
+
73
+ Generate a [mountable engine](http://guides.rubyonrails.org/engines.html):
74
+ ```sh
75
+ $ bin/rails plugin new admin --mountable
76
+ ```
77
+
78
+ Add the engine to the application's `Gemfile`:
79
+ ```ruby
80
+ gem "admin", path: "admin"
81
+ ```
82
+
83
+ Mount the engine in the application's `config/routes.rb`:
84
+ ```ruby
85
+ mount Admin::Engine, at: "admin"
86
+ ```
87
+
88
+ Add the gem to the engine's gemspec, `admin/admin.gemspec`:
89
+ ```ruby
90
+ s.add_dependency "goodmin", "~> x.x.x"
91
+ ```
92
+
93
+ Bundle, then run the install generator within the scope of the engine, i.e. note the leading `admin/`:
94
+ ```sh
95
+ $ bundle install
96
+ $ admin/bin/rails generate goodmin:install
97
+ ```
98
+
99
+ Goodmin should be up and running at `localhost:3000/admin`
100
+
101
+ ### Installation artefacts
102
+
103
+ Installing Goodmin does a number of things to the Rails application.
104
+
105
+ The application controller is modified as such:
106
+ ```ruby
107
+ class ApplicationController < ActionController::Base
108
+ include Goodmin::ApplicationController
109
+ end
110
+ ```
111
+
112
+ Require statements are placed in both `app/assets/javascripts/application.js` and `app/assets/stylesheets/application.css`.
113
+
114
+ If Goodmin was installed inside an engine, a `require "goodmin"` statement is placed in `{namespace}/lib/{namespace}.rb`.
115
+
116
+ An `app/views/shared/_navigation.html.erb` partial is created.
117
+
118
+ And finally, the `app/views/layouts` folder is removed by default, so as not to interfere with the Goodmin layouts. It can be added back in case you wish to override the built in layouts.
119
+
120
+ ## Getting started
121
+
122
+ Goodmin deals primarily with resources. A resource is something that can be administered through the Goodmin user interface, often a Rails model. Let's say the application has an `Article` model with attributes such as `title`, `body` and `published`. To get going quickly, we can use a generator:
123
+
124
+ ```sh
125
+ $ bin/rails generate goodmin:resource article title published
126
+ ```
127
+
128
+ Or for an engine install:
129
+ ```sh
130
+ $ admin/bin/rails generate goodmin:resource article title published
131
+ ```
132
+
133
+ This does a number of things.
134
+
135
+ It inserts a route in the `config/routes.rb` file:
136
+
137
+ ```ruby
138
+ resources :articles
139
+ ```
140
+
141
+ It inserts a `navbar_item` in the `app/views/shared/_navigation.html.erb` partial:
142
+
143
+ ```erb
144
+ <%= navbar_item Article %>
145
+ ```
146
+
147
+ If Goodmin was installed inside an engine, it creates a model class:
148
+
149
+ ```ruby
150
+ module Admin
151
+ class Article < ::Article
152
+ end
153
+ end
154
+ ```
155
+
156
+ It creates a controller:
157
+
158
+ ```ruby
159
+ class ArticlesController < ApplicationController
160
+ include Goodmin::Resources::ResourceController
161
+ end
162
+ ```
163
+
164
+ It creates a resource object:
165
+
166
+ ```ruby
167
+ class ArticleResource
168
+ include Goodmin::Resources::Resource
169
+
170
+ index do
171
+ attribute :title
172
+ attribute :published
173
+ end
174
+
175
+ show do
176
+ attribute :title
177
+ attribute :published
178
+ end
179
+
180
+ form do
181
+ attribute :title
182
+ attribute :published
183
+ end
184
+ end
185
+ ```
186
+
187
+ Using the `index` block we can control what fields are displayed in the table listing, using the `show` block we can control what fields are displayed on the show page, and using the `form` block we can control what fields are available in the new and edit forms. We can, for instance, add the `body` field to the `form` block to make it appear in forms:
188
+
189
+ ```ruby
190
+ form do
191
+ attribute :title
192
+ attribute :body
193
+ attribute :published
194
+ end
195
+ ```
196
+
197
+ For quick prototyping, we could build the parameters this way (if appropriate).
198
+ ```ruby
199
+ show do
200
+ Article.column_names.each { |col| attribute col.to_sym }
201
+ end
202
+ ```
203
+
204
+ By now we have a basic admin interface for managing articles.
205
+
206
+ ## Resources
207
+
208
+ As we saw in the example above, resources are divided into controllers and resource objects. Actions, redirects, params permitting etc go in the controller while resource fetching, building, sorting, filtering etc go in the resource object. This makes the resource objects small and easy to test.
209
+
210
+ We have already seen three block DSL methods at play: `index`, `show` and `form`. We will now look at some additional resource concepts.
211
+
212
+ ### Scopes
213
+
214
+ Scopes are a way of sectioning resources, useful for quick navigation, and can be created as follows:
215
+
216
+ ```ruby
217
+ class ArticleResource
218
+ include Goodmin::Resources::Resource
219
+
220
+ scope :unpublished, default: true
221
+ scope :published
222
+
223
+ def scope_unpublished(resources)
224
+ resources.where(published: false)
225
+ end
226
+
227
+ def scope_published(resources)
228
+ resources.where(published: true)
229
+ end
230
+ end
231
+ ```
232
+
233
+ ### Filters
234
+
235
+ Filters offer great flexibility when it comes to searching for resources, and can be created as follows:
236
+
237
+ ```ruby
238
+ class ArticleResource
239
+ include Goodmin::Resources::Resource
240
+
241
+ filter :title
242
+
243
+ def filter_title(resources, value)
244
+ resources.where("title LIKE ?", "%#{value}%")
245
+ end
246
+ end
247
+ ```
248
+
249
+ There are three types of filters: `string`, `select` and `multiselect`, specified using the `as` parameter.
250
+
251
+ When using `select` or `multiselect`, a collection must be specified. The collection must conform to the format used by Rails `options_for_select` helpers. It can be either an array consisting of name/value tuples, or a collection of ActiveRecords.
252
+
253
+ ```ruby
254
+ filter :category, as: :select, collection: -> { [["News", 1], ["Posts", 2]] }
255
+ ```
256
+
257
+ When specifying a collection of ActiveRecords, two additional parameters, `option_text` and `option_value` can be specified. They default to `to_s` and `id` respectively.
258
+
259
+ ```ruby
260
+ filter :category, as: :select, collection: -> { Category.all }, option_text: "title"
261
+ ```
262
+
263
+ ### Batch actions
264
+
265
+ Batch actions can be created as follows:
266
+
267
+ ```ruby
268
+ class ArticleResource
269
+ include Goodmin::Resources::Resource
270
+
271
+ batch_action :publish
272
+ batch_action :unpublish
273
+ batch_action :destroy, confirm: true
274
+
275
+ def batch_action_publish(resources)
276
+ resources.each(&:publish!)
277
+ end
278
+ end
279
+ ```
280
+
281
+ In addition, batch actions can be defined per scope using `only` and `except`:
282
+
283
+ ```ruby
284
+ batch_action :publish, only: [:unpublished]
285
+ batch_action :unpublish, only: [:published]
286
+ ```
287
+
288
+ If you wish to implement your own redirect after a batch action, it needs to be implemented in the controller:
289
+
290
+ ```ruby
291
+ class ArticlesController < ApplicationController
292
+ include Goodmin::Resources::ResourceController
293
+
294
+ private
295
+
296
+ def redirect_after_batch_action_publish
297
+ articles_path(scope: :published)
298
+ end
299
+ end
300
+ ```
301
+
302
+ If you are using Goodmin's built in authorization functionality you must [authorize your batch actions in your policy](#batch-action-authorization).
303
+
304
+ ### Custom ordering
305
+
306
+ By default, Goodmin supports ordering of database columns in the index view table. However, it cannot automatically sort associations, custom attributes and so on.
307
+ If you want to order something that Goodmin doesn't support out of the box, or you just want to customize how a columns is ordered, you can implement your own ordering functionality in the resource object by creating a `order_by_<attribute>` method.
308
+
309
+ ```ruby
310
+ class ArticleResource
311
+ include Goodmin::Resources::Resource
312
+
313
+ index do
314
+ attribute :title
315
+ attribute :author
316
+ end
317
+
318
+ # resources is an ActiveRecord::Relation object
319
+ # direction is the order direction ("asc" or "desc")
320
+ def order_by_author(resources, direction)
321
+ resources.joins(:authors).order("authors.name #{direction}")
322
+ end
323
+ end
324
+ ```
325
+
326
+ ### Resource fetching, building and saving
327
+
328
+ Resources are made available to the views through instance variables. The index view can access the resources using `@resources` while show, new and edit can access the single resource using `@resource`. In addition, the resource class is available as `@resource_class` and the resource object is available as `@resource_service`.
329
+
330
+ In order to modify resource fetching and construction, these methods can be overridden per resource:
331
+
332
+ - `resource_class`
333
+ - `resources_relation`
334
+ - `resources`
335
+ - `find_resource`
336
+ - `build_resource`
337
+ - `create_resource`
338
+ - `update_resource`
339
+ - `destroy_resource`
340
+
341
+ To change the class name of the resource from the default based on the resource class name:
342
+
343
+ ```ruby
344
+ class ArticleResource
345
+ include Goodmin::Resources::Resource
346
+
347
+ def resource_class
348
+ FooArticle
349
+ end
350
+ end
351
+ ```
352
+
353
+ To scope resources for quering and building, e.g. based on the signed in user:
354
+
355
+ ```ruby
356
+ class ArticleResource
357
+ include Goodmin::Resources::Resource
358
+
359
+ # The signed in admin user is available to all resource objects via the options hash
360
+ def resources_relation
361
+ super.where(user: options[:admin_user])
362
+ end
363
+ end
364
+ ```
365
+
366
+ To add to the index page resources query, e.g. to change the default order:
367
+
368
+ ```ruby
369
+ class ArticleResource
370
+ include Goodmin::Resources::Resource
371
+
372
+ def resources(params)
373
+ super(params).order(author: :desc)
374
+ end
375
+ end
376
+ ```
377
+
378
+ To change the way a resource is fetched for `show`, `edit`, `update` and `destroy` actions:
379
+
380
+ ```ruby
381
+ class ArticleResource
382
+ include Goodmin::Resources::Resource
383
+
384
+ def find_resource(id)
385
+ resources_relation.find_by(slug: id)
386
+ end
387
+ end
388
+ ```
389
+
390
+ To change the way a resource is constructed for `new` and `create` actions:
391
+
392
+ ```ruby
393
+ class ArticleResource
394
+ include Goodmin::Resources::Resource
395
+
396
+ def build_resource(_params)
397
+ article = super
398
+ article.setup_more_things
399
+ article
400
+ end
401
+ end
402
+ ```
403
+
404
+ To change the way a resource is saved in the `create` action:
405
+
406
+ ```ruby
407
+ class ArticleResource
408
+ include Goodmin::Resources::Resource
409
+
410
+ # This method should return true or false
411
+ def create_resource(resource)
412
+ resource.save_in_some_interesting_way
413
+ end
414
+ end
415
+ ```
416
+
417
+ To change the way a resource is saved in the `update` action:
418
+
419
+ ```ruby
420
+ class ArticleResource
421
+ include Goodmin::Resources::Resource
422
+
423
+ # This method should return true or false
424
+ def update_resource(resource, params)
425
+ resource.assign_attributes(params)
426
+ resource.save_in_some_interesting_way
427
+ end
428
+ end
429
+ ```
430
+
431
+ To change the way a resource is destroyed in the `destroy` action:
432
+
433
+ ```ruby
434
+ class ArticleResource
435
+ include Goodmin::Resources::Resource
436
+
437
+ def destroy_resource(resource)
438
+ resource.paranoid_destroy
439
+ end
440
+ end
441
+ ```
442
+
443
+ #### Strong parameters
444
+
445
+ When using the `form` block, parameters are automatically permitted based on the declared attributes. If building a custom form, see the [forms](#forms) section, parameters can be permitted by overriding the `resource_params` method in the controller:
446
+
447
+ ```ruby
448
+ class ArticlesController < ApplicationController
449
+ include Goodmin::Resources::ResourceController
450
+
451
+ private
452
+
453
+ def resource_params
454
+ params.require(:article).permit(:title, :body)
455
+ end
456
+ end
457
+ ```
458
+
459
+ #### Passing parameters to the resource object
460
+
461
+ Sometimes you want to pass additional params to the resource object, other that those passed in `resource_params`. In order to do this, you need to pass them along when initializing the resource object in the controller:
462
+
463
+ ```ruby
464
+ class ArticlesController < ApplicationController
465
+ include Goodmin::Resources::ResourceController
466
+
467
+ private
468
+
469
+ def resource_service
470
+ service = super
471
+ service.options[:some_param] = params[:some_param]
472
+ service
473
+ end
474
+ end
475
+ ```
476
+
477
+ You can then access it from the resource object:
478
+
479
+ ```ruby
480
+ class ArticleResource
481
+ include Goodmin::Resources::Resource
482
+
483
+ def some_method
484
+ options[:some_param]
485
+ end
486
+ end
487
+ ```
488
+
489
+ ### Redirecting
490
+
491
+ By default the user is redirected to the resource show page after create and update, and to the index page after destroy. To change this, there are four controller methods that can be overridden: `redirect_after_create`, `redirect_after_update`, `redirect_after_save`, and `redirect_after_destroy`.
492
+
493
+ For instance, to have the article controller redirect to the index page after both create and update:
494
+
495
+ ```ruby
496
+ class ArticlesController < ApplicationController
497
+ include Goodmin::Resources::ResourceController
498
+
499
+ private
500
+
501
+ def redirect_after_save
502
+ articles_path
503
+ end
504
+ end
505
+ ```
506
+
507
+ Or, to have the article controller redirect to the index page after create and the edit page after update:
508
+
509
+ ```ruby
510
+ class ArticlesController < ApplicationController
511
+ include Goodmin::Resources::ResourceController
512
+
513
+ private
514
+
515
+ def redirect_after_create
516
+ articles_path
517
+ end
518
+
519
+ def redirect_after_update
520
+ edit_article_path(@resource)
521
+ end
522
+ end
523
+ ```
524
+
525
+ If you wish to change the behaviour for every resource controller, consider creating a common resource controller that your other controllers can inherit from:
526
+
527
+ ```ruby
528
+ class ResourceController < ApplicationController
529
+ include Goodmin::Resources::ResourceController
530
+
531
+ private
532
+
533
+ def redirect_after_save
534
+ resource_class.model_name.route_key.to_sym
535
+ end
536
+ end
537
+ ```
538
+
539
+ ### Pagination
540
+
541
+ If you wish to change the number of resources per page, you can override the `per_page` method in the resource object:
542
+
543
+ ```ruby
544
+ class ArticlesResource
545
+ include Goodmin::Resources::Resource
546
+
547
+ def per_page
548
+ 50
549
+ end
550
+ end
551
+ ```
552
+
553
+ ### Exporting
554
+
555
+ The `export` block in the resource object makes it possible to mark attributes or methods on the model as exportable. When implemented, an export button will appear on the index page with options for both CSV and JSON export.
556
+
557
+ ```ruby
558
+ class ArticlesResource
559
+ include Goodmin::Resources::Resource
560
+
561
+ export do
562
+ attribute :id
563
+ attribute :title
564
+ attribute :created_at
565
+ attribute :updated_at
566
+ end
567
+ end
568
+ ```
569
+
570
+ ### Nested resources
571
+
572
+ Nested resources can be implemented by nesting your routes:
573
+
574
+ ```ruby
575
+ resources :blogs do
576
+ resources :blog_posts
577
+ end
578
+ ```
579
+
580
+ This will set up scoping of the nested resource as well as correct links in the breadcrumb.
581
+
582
+ If you want to add a link to the nested resource from the parent's show and edit pages, you can add the following to the resource object:
583
+
584
+ ```ruby
585
+ class BlogResource
586
+ include Goodmin::Resources::Resource
587
+
588
+ has_many :blog_posts
589
+ end
590
+ ```
591
+
592
+ Otherwise, simply add links as you see fit using partial overrides.
593
+
594
+ ## Views
595
+
596
+ It's easy to override view templates and partials in Goodmin, both globally and per resource. All you have to do is place a file with an identical name in your `app/views` directory. For instance, to override the `goodmin/resource/index.html.erb` template for all resources, place a file under `app/views/resource/index.html.erb`. If you only wish to override it for articles, place it instead under `app/views/articles/index.html.erb`.
597
+
598
+ You can also inherit from the default template as such:
599
+ ```erb
600
+ <%= render template: "goodmin/resource/show" %>
601
+
602
+ <p>Append stuff here</p>
603
+ ```
604
+
605
+ If you wish to customize the content of a table column, you can place a partial under `app/views/{resource}/columns/{column_name}.html.erb`, e.g. `app/views/articles/columns/_title.html.erb`. The resource is available to the partial through the `resource` variable.
606
+
607
+ The full list of templates and partials that can be overridden [can be found here](https://github.com/varvet/goodmin/tree/master/app/views/goodmin).
608
+
609
+ ### Forms
610
+
611
+ Goodmin uses a block-based DSL to define form fields in the resource object. The `form` block supports plain attribute declarations as well as richer layout components.
612
+
613
+ #### Basic usage
614
+
615
+ ```ruby
616
+ class ArticleResource
617
+ include Goodmin::Resources::Resource
618
+
619
+ form do
620
+ attribute :title
621
+ attribute :body
622
+ attribute :published
623
+ end
624
+ end
625
+ ```
626
+
627
+ #### Using HTML tags
628
+
629
+ Any of the standard HTML container tags (`div`, `span`, `p`, `fieldset`, `article`, `header`, `footer`, `main`, `h1`–`h6`, `ul`, `ol`, `li`, `dl`, `dt`, `dd`) can be used directly inside a `form` block to add arbitrary markup:
630
+
631
+ ```ruby
632
+ form do
633
+ div(class: "col-md-6") do
634
+ attribute :title
635
+ end
636
+ div(class: "col-md-6") do
637
+ attribute :published
638
+ end
639
+ end
640
+ ```
641
+
642
+ #### Built-in form components
643
+
644
+ Goodmin ships with three higher-level layout components.
645
+
646
+ **`row` / `col`** — Bootstrap grid helpers:
647
+
648
+ ```ruby
649
+ form do
650
+ row do
651
+ col(size: 6) { attribute :title }
652
+ col(size: 6) { attribute :published }
653
+ end
654
+ attribute :body
655
+ end
656
+ ```
657
+
658
+ `col` accepts an optional `size` keyword (default: `12`) that maps to Bootstrap's `col-md-*` classes.
659
+
660
+ **`section`** — a titled, optionally described group of fields:
661
+
662
+ ```ruby
663
+ form do
664
+ section(title: "Content", description: "Fill in the article content below.") do
665
+ attribute :title
666
+ attribute :body
667
+ end
668
+ section(title: "Meta") do
669
+ attribute :published
670
+ end
671
+ end
672
+ ```
673
+
674
+ Both `title` and `description` are optional.
675
+
676
+ #### Registering custom form DSL components
677
+
678
+ You can extend the form DSL with your own components by creating a class that includes `Goodmin::Resources::FormComponent` and registering it with `FormBuilder.register_component`.
679
+
680
+ **Step 1 – Create the component:**
681
+
682
+ ```ruby
683
+ class CardComponent
684
+ include Goodmin::Resources::FormComponent
685
+
686
+ def initialize(children, heading:)
687
+ super(children)
688
+ @heading = heading
689
+ end
690
+
691
+ def render(view_context, f)
692
+ view_context.content_tag(:div, class: "card") do
693
+ view_context.content_tag(:div, @heading, class: "card-header") +
694
+ view_context.content_tag(:div, class: "card-body") do
695
+ view_context.render_form_nodes(children, f)
696
+ end
697
+ end
698
+ end
699
+ end
700
+ ```
701
+
702
+ **Step 2 – Register the component** (e.g. in an initializer):
703
+
704
+ ```ruby
705
+ Goodmin::Resources::FormBuilder.register_component(:card, CardComponent)
706
+ ```
707
+
708
+ **Step 3 – Use it in a form block:**
709
+
710
+ ```ruby
711
+ form do
712
+ card(heading: "Details") do
713
+ attribute :title
714
+ attribute :body
715
+ end
716
+ end
717
+ ```
718
+
719
+ Strong parameters are automatically derived from all attributes declared inside the component.
720
+
721
+ #### Custom fields
722
+
723
+ Goodmin automatically maps database column types to built-in field classes (`Fields::String`, `Fields::Text`, `Fields::Boolean`, `Fields::Date`, `Fields::DateTime`, `Fields::Number`, and `Fields::Association`). When you need different rendering or behaviour for a particular attribute, you can create your own field class and tell Goodmin to use it.
724
+
725
+ **Step 1 – Create the field class:**
726
+
727
+ A custom field must inherit from `Goodmin::Fields::Base`. Place it in `app/goodmin/fields/` — Goodmin automatically loads files from that directory under the `Goodmin::Fields` namespace. Override `value` if you need to transform the raw attribute value, and provide ERB partials to control how the field is rendered.
728
+
729
+ ```ruby
730
+ # app/goodmin/fields/color.rb
731
+ module Goodmin
732
+ module Fields
733
+ class Color < Base
734
+ # Optional: transform the value before it reaches the partial
735
+ def value
736
+ record.public_send(attribute).to_s.upcase
737
+ end
738
+ end
739
+ end
740
+ end
741
+ ```
742
+
743
+ **Step 2 – Create the partials:**
744
+
745
+ Place partials under `app/views/goodmin/fields/<field_type>/` where `<field_type>` is the underscored class name (e.g. `color` for `Goodmin::Fields::Color`). Each context — `_form.html.erb`, `_index.html.erb`, and `_show.html.erb` — can be overridden independently.
746
+
747
+ ```erb
748
+ <%# app/views/goodmin/fields/color/_form.html.erb %>
749
+ <div class="form-group">
750
+ <%= f.label field.attribute %>
751
+ <%= f.color_field field.attribute, class: "form-control" %>
752
+ </div>
753
+ ```
754
+
755
+ ```erb
756
+ <%# app/views/goodmin/fields/color/_index.html.erb %>
757
+ <span class="color-swatch" style="background: <%= field.value %>"><%= field.value %></span>
758
+ ```
759
+
760
+ ```erb
761
+ <%# app/views/goodmin/fields/color/_show.html.erb %>
762
+ <span class="color-swatch" style="background: <%= field.value %>"><%= field.value %></span>
763
+ ```
764
+
765
+ **Step 3 – Use the field in a resource:**
766
+
767
+ Pass the field class via the `field:` option when declaring an attribute in any block (`index`, `show`, `form`, or `export`):
768
+
769
+ ```ruby
770
+ class ArticleResource
771
+ include Goodmin::Resources::Resource
772
+
773
+ index do
774
+ attribute :color, field: Goodmin::Fields::Color
775
+ end
776
+
777
+ show do
778
+ attribute :color, field: Goodmin::Fields::Color
779
+ end
780
+
781
+ form do
782
+ attribute :color, field: Goodmin::Fields::Color
783
+ end
784
+ end
785
+ ```
786
+
787
+ **Passing options to a custom field:**
788
+
789
+ You can pass arbitrary keyword arguments alongside `field:` when declaring an attribute. They are forwarded to the field instance and accessible via `field.options` — both inside the field class and inside its ERB partials.
790
+
791
+ ```ruby
792
+ # Resource service
793
+ index do
794
+ attribute :color, field: Goodmin::Fields::Color, label: "Hex colour", swatch: true
795
+ end
796
+ ```
797
+
798
+ ```ruby
799
+ # app/goodmin/fields/color.rb
800
+ module Goodmin
801
+ module Fields
802
+ class Color < Base
803
+ def value
804
+ # options[:swatch] is true/false, etc.
805
+ record.public_send(attribute).to_s.upcase
806
+ end
807
+ end
808
+ end
809
+ end
810
+ ```
811
+
812
+ ```erb
813
+ <%# app/views/goodmin/fields/color/_index.html.erb %>
814
+ <% if field.options[:swatch] %>
815
+ <span class="color-swatch" style="background: <%= field.value %>"></span>
816
+ <% end %>
817
+ <%= field.value %>
818
+ ```
819
+
820
+ #### Custom form partials
821
+
822
+ Oftentimes, the default form provided by Goodmin doesn't cut it. The `goodmin/resource/_form.html.erb` partial is therefore one of the most common to override per resource.
823
+
824
+ Goodmin comes with its own FormBuilder that automatically generates bootstrapped markup. It is based on the [Rails Bootstrap Forms](https://github.com/bootstrap-ruby/rails-bootstrap-forms) FormBuilder, and all its methods are directly available. In addition it has a few convenience methods that can be leveraged.
825
+
826
+ The `input` method will automatically detect the type of field from the database and generate an appropriate form field:
827
+
828
+ ```ruby
829
+ form_for @resource do |f|
830
+ f.input :attribute
831
+ end
832
+ ```
833
+
834
+ ### Navigation
835
+
836
+ Goodmin comes with built in view helpers for generating the navbar.
837
+
838
+ The `navbar_item` helper generates a link in the navbar. It can be used in a number of different ways.
839
+
840
+ ```ruby
841
+ # Links to the index page of the article resource
842
+ navbar_item Article
843
+
844
+ # Links to a custom path with a custom link text
845
+ navbar_item Article, articles_path(scope: :published) do
846
+ "Published articles"
847
+ end
848
+
849
+ # Links to a custom path with a custom link text without specifying resource
850
+ navbar_item "Some text", some_path
851
+ ```
852
+
853
+ The `show` option can be passed a proc that evaluates to true or false. This is used to control if the link should be shown or not. By default it checks against the resource policy object if authorization is enabled.
854
+
855
+ ```ruby
856
+ navbar_item Article, show: -> { show? }
857
+ ```
858
+
859
+ The `icon` option can be passed a glyphicon:
860
+
861
+ ```ruby
862
+ navbar_item Article, icon: "book"
863
+ ```
864
+
865
+ The `navbar_dropdown` and `navbar_divider` helpers can be used to build dropdown menus.
866
+
867
+ ```ruby
868
+ navbar_dropdown "Multiple things" do
869
+ navbar_item Article
870
+ navbar_item Comment
871
+ navbar_divider
872
+ navbar_item User
873
+ end
874
+ ```
875
+
876
+ ## Authentication
877
+
878
+ Multiple authentication scenarios are supported. Goodmin comes with a lightweight built in authentication solution that can be used to sign in to the admin section via the admin interface. In addition, when running an admin engine, it is possible to set up a shared authentication solution so that administrators can sign in via the main app.
879
+
880
+ ### Built in authentication
881
+
882
+ This example uses the built in authentication solution. Authentication is isolated to the admin section and administrators sign in via the admin interface.
883
+
884
+ Goodmin comes with a generator that creates an admin user model and enables the built in authentication:
885
+
886
+ ```sh
887
+ $ bin/rails generate goodmin:authentication
888
+ $ bin/rake db:migrate
889
+ ```
890
+
891
+ Please note: when installing to an admin engine, the migration needs to be moved to the main app before it can be found by `db:migrate`. Rails has a solution in place for this:
892
+
893
+ ```sh
894
+ $ admin/bin/rails generate goodmin:authentication
895
+ $ bin/rake admin:install:migrations
896
+ $ bin/rake db:migrate
897
+ ```
898
+
899
+ A model is generated:
900
+
901
+ ```ruby
902
+ class AdminUser < ActiveRecord::Base
903
+ include Goodmin::Authentication::User
904
+
905
+ def self.login_column
906
+ :email
907
+ end
908
+ end
909
+ ```
910
+
911
+ By default the user model is called `AdminUser`. If you'd like to change this, you can pass an argument to the authentication generator:
912
+
913
+ ```
914
+ $ bin/rails generate goodmin:authentication SuperUser
915
+ or for an engine:
916
+ $ admin/bin/rails generate goodmin:authentication SuperUser
917
+ ```
918
+
919
+ By default the model is generated with an `email` field as the login column. This can changed in the migration prior to migrating if, for instance, a `username` column is more appropriate.
920
+
921
+ The following route is generated:
922
+
923
+ ```ruby
924
+ resource :session, only: [:new, :create, :destroy]
925
+ ```
926
+
927
+ Along with a sessions controller:
928
+
929
+ ```ruby
930
+ class SessionsController < ApplicationController
931
+ include Goodmin::Authentication::SessionsController
932
+ end
933
+ ```
934
+
935
+ Finally, the application controller is modified:
936
+
937
+ ```ruby
938
+ class ApplicationController < ActionController::Base
939
+ include Goodmin::ApplicationController
940
+ include Goodmin::Authentication
941
+
942
+ def admin_user_class
943
+ AdminUser
944
+ end
945
+ end
946
+ ```
947
+
948
+ Authentication is now required when visiting the admin section.
949
+
950
+ ### Shared authentication
951
+
952
+ This example uses [Devise](https://github.com/plataformatec/devise) to set up a shared authentication solution between the main app and an admin engine. Administrators sign in and out via the main application.
953
+
954
+ There is no need to run a generator in this instance. Simply add the authentication module to the admin application controller like so:
955
+
956
+ ```ruby
957
+ module Admin
958
+ class ApplicationController < ActionController::Base
959
+ include Goodmin::ApplicationController
960
+ include Goodmin::Authentication
961
+ end
962
+ end
963
+ ```
964
+
965
+ Provided you have `User` model set up with Devise in the main application, override the following three methods in the admin application controller:
966
+
967
+ ```ruby
968
+ module Admin
969
+ class ApplicationController < ActionController::Base
970
+ include Goodmin::ApplicationController
971
+ include Goodmin::Authentication
972
+
973
+ def authenticate
974
+ authenticate_user!
975
+ end
976
+
977
+ def admin_user
978
+ current_user
979
+ end
980
+
981
+ def admin_user_signed_in?
982
+ user_signed_in?
983
+ end
984
+ end
985
+ end
986
+ ```
987
+
988
+ The admin section is now authenticated using Devise.
989
+
990
+ ### Disable authentication
991
+
992
+ If you want to disable authentication for a single controller or controller action, use the following `before_action`:
993
+
994
+ ```ruby
995
+ class ArticlesController < ApplicationController
996
+ prepend_before_action :disable_authentication
997
+ end
998
+ ```
999
+
1000
+ ## Authorization
1001
+
1002
+ In order to enable authorization, authentication must first be enabled. See the previous section. The Goodmin authorization system uses [Pundit](https://github.com/elabs/pundit).
1003
+
1004
+ Add the authorization module to the application controller:
1005
+
1006
+ ```ruby
1007
+ class ApplicationController < ActionController::Base
1008
+ include Goodmin::ApplicationController
1009
+ include Goodmin::Authentication
1010
+ include Goodmin::Authorization
1011
+
1012
+ ...
1013
+ end
1014
+ ```
1015
+
1016
+ Policies can be generated using the following command:
1017
+
1018
+ ```sh
1019
+ $ bin/rails generate goodmin:policy article
1020
+ ```
1021
+
1022
+ This file `app/policies/article_policy.rb` will be created:
1023
+
1024
+ ```ruby
1025
+ class ArticlePolicy < Goodmin::Authorization::Policy
1026
+ end
1027
+ ```
1028
+
1029
+ Permissions are specified by implementing methods on this class. Two methods are available to the methods, `user` and `record`, the signed in user and the record being authorized. An implemented policy can look something like this:
1030
+
1031
+ ```ruby
1032
+ class ArticlePolicy < Goodmin::Authorization::Policy
1033
+ def index?
1034
+ true
1035
+ end
1036
+
1037
+ def show?
1038
+ true
1039
+ end
1040
+
1041
+ def create?
1042
+ user.editor?
1043
+ end
1044
+
1045
+ def update?
1046
+ user.editor? && record.unpublished?
1047
+ end
1048
+
1049
+ def destroy?
1050
+ update?
1051
+ end
1052
+
1053
+ def batch_action_destroy?
1054
+ destroy?
1055
+ end
1056
+ end
1057
+ ```
1058
+
1059
+ That is, everyone can list and view articles, only editors can create them, and only unpublished articles can be updated and destroyed.
1060
+
1061
+ ### Handle unauthorized access
1062
+
1063
+ When a user is not authorized to access a resource, a `Pundit::NotAuthorizedError` is raised. By default this error is rescued by Goodmin and turned into a status code `403 Forbidden` response. If you want to change this behaviour you can rescue the error yourself in the appropriate `ApplicationController`:
1064
+
1065
+ ```ruby
1066
+ class ApplicationController < ActionController::Base
1067
+ include Goodmin::ApplicationController
1068
+ include Goodmin::Authentication
1069
+ include Goodmin::Authorization
1070
+
1071
+ # Renders 404 page and returns status code 404.
1072
+ rescue_from Pundit::NotAuthorizedError do
1073
+ render file: "#{Rails.root}/public/404.html", status: 404, layout: false
1074
+ end
1075
+ end
1076
+ ```
1077
+
1078
+ ### Override policy object
1079
+
1080
+ If you wish to specify what policy to use manually, override the following method in your model. It does not have to be an ActiveRecord object, but any object will do.
1081
+
1082
+ ```ruby
1083
+ class Article
1084
+ def policy_class(_record)
1085
+ FooArticlePolicy
1086
+ end
1087
+ end
1088
+ ```
1089
+
1090
+ ### Batch action authorization
1091
+
1092
+ Batch actions must be authorized in your policy if you are using Goodmin's built in authorization functionality. The policy method is called with the relation containing all records to be processed.
1093
+
1094
+ ```ruby
1095
+ class ArticlePolicy < Goodmin::Authorization::Policy
1096
+ def batch_action_destroy?
1097
+ record.all? { |r| r.user_id == user.id }
1098
+ end
1099
+ end
1100
+ ```
1101
+
1102
+ ### Disable authorization
1103
+
1104
+ If you want to disable authorization for a single controller or controller action, use the following `before_action`:
1105
+
1106
+ ```ruby
1107
+ class ArticlesController < ApplicationController
1108
+ prepend_before_action :disable_authorization
1109
+ end
1110
+ ```
1111
+
1112
+ ### Authorization in Engines
1113
+
1114
+ When Goodmin is installed as an engine, it expects policies to be defined
1115
+ within the engine: eg. `Admin::ArticlePolicy` defined in
1116
+ `admin/app/policies/article_policy.rb`.
1117
+
1118
+ If your admin application is itself broken up into several engines, then
1119
+ either
1120
+
1121
+ 1. the policies for those engines need to live in the main engine, or
1122
+ 2. those engines need to be namespaced under the namespace of the main engine.
1123
+
1124
+ Here is one example of a directory structure for approach 2:
1125
+
1126
+ ```
1127
+ admin
1128
+ ├── app
1129
+   │   └── policies
1130
+ │      └── admin
1131
+ │      └── article_policy.rb
1132
+ └── engines
1133
+    └── content
1134
+      └── policies
1135
+       └── admin
1136
+    └── content
1137
+       └── text_block_policy.rb
1138
+ app
1139
+   └── models
1140
+    └── article.rb
1141
+ engines
1142
+   └── content
1143
+      └── models
1144
+    └── content
1145
+       └── text_block.rb
1146
+ ```
1147
+ ```ruby
1148
+ # admin/engines/content/policies/admin/content/text_block_policy.rb
1149
+ module Admin
1150
+ module Content
1151
+ class TextBlockPolicy < ::Admin::ApplicationPolicy
1152
+ end
1153
+ end
1154
+ end
1155
+ ```
1156
+
1157
+ ## Localization
1158
+
1159
+ Goodmin supports localization out of the box. For a list of translatable strings, [look here](https://github.com/varvet/goodmin/blob/master/config/locales/en.yml).
1160
+
1161
+ Strings can be translated both globally and per resource, similar to how views work. For instance, to translate the `goodmin.batch_actions.buttons.select_all` string globally:
1162
+
1163
+ ```yml
1164
+ goodmin:
1165
+ batch_actions:
1166
+ buttons:
1167
+ select_all: {translation}
1168
+ ```
1169
+
1170
+ Or, translate for a specific resource:
1171
+
1172
+ ```yml
1173
+ goodmin:
1174
+ article:
1175
+ batch_actions:
1176
+ buttons:
1177
+ select_all: {translation}
1178
+ ```
1179
+
1180
+ In addition, all scopes, filters and batch actions that are added, can be localized:
1181
+
1182
+ ```yml
1183
+ goodmin:
1184
+ article:
1185
+ batch_actions:
1186
+ labels:
1187
+ publish: {translation}
1188
+ unpublish: {translation}
1189
+ filters:
1190
+ labels:
1191
+ title: {translation}
1192
+ scopes:
1193
+ labels:
1194
+ unpublished: {translation}
1195
+ published: {translation}
1196
+ ```
1197
+
1198
+ Goodmin comes with built in support for English and Swedish.
1199
+
1200
+ There is a view helper available named `translate_scoped` that can be used in overridden views. Please see the source code for information on how to use it.
1201
+
1202
+ ## JavaScript
1203
+
1204
+ Goodmin comes with a small set of JavaScript components and APIs.
1205
+
1206
+ ### Datetimepickers
1207
+
1208
+ Make a [bootstrap-datetimepicker](https://github.com/Eonasdan/bootstrap-datetimepicker) out of a text field:
1209
+
1210
+ ```ruby
1211
+ f.date_field :date
1212
+ f.datetime_field :date
1213
+ ```
1214
+
1215
+ If the field is added post page render, it can be initialized manually:
1216
+
1217
+ ```js
1218
+ Goodmin.Datetimepickers.initializeDatepicker($el);
1219
+ Goodmin.Datetimepickers.initializeTimepicker($el);
1220
+ Goodmin.Datetimepickers.initializeDatetimepicker($el);
1221
+ ```
1222
+
1223
+ Additional options can be passed down to bootstrap-datetimepicker:
1224
+
1225
+ ```js
1226
+ Goodmin.Datetimepickers.initializeDatetimepicker($el, {
1227
+ useMinutes: false,
1228
+ useSeconds: false
1229
+ });
1230
+ ```
1231
+
1232
+ If you wish to translate the datetimepicker, change `moment/en-gb` in your `app/assets/javascripts/application.js` to your desired locale:
1233
+
1234
+ ```js
1235
+ //= require moment
1236
+ //= require moment/{locale} // e.g. moment/sv
1237
+ //= require goodmin
1238
+ ```
1239
+
1240
+ Please note that the datepickers default to en-GB, not en-US, because Rails cannot automatically parse en-US dates.
1241
+
1242
+ To use an alternative format, use the format option.
1243
+
1244
+ ```js
1245
+ Goodmin.Datetimepickers.initializeDatepicker($elems, {
1246
+ format: 'YYYY-MM-DD'
1247
+ });
1248
+ ```
1249
+
1250
+ ### Stimulus controllers
1251
+
1252
+ Goodmin uses [Stimulus](https://stimulus.hotwired.dev/) and maintains its own importmap instance (`Goodmin.importmap`) separate from your application's default importmap. Controllers are auto-discovered via `eagerLoadControllersFrom`, meaning any module pinned under the `controllers/` prefix in `Goodmin.importmap` is automatically registered as a Stimulus controller.
1253
+
1254
+ #### Registering a controller from a standalone app
1255
+
1256
+ Create your controller under `app/javascript/controllers/`:
1257
+
1258
+ ```js
1259
+ // app/javascript/controllers/my_controller.js
1260
+ import { Controller } from "@hotwired/stimulus"
1261
+
1262
+ export default class extends Controller {
1263
+ connect() {
1264
+ console.log("my controller connected")
1265
+ }
1266
+ }
1267
+ ```
1268
+
1269
+ Propshaft serves `app/javascript` automatically in a Rails app, so you only need to pin the controller in `Goodmin.importmap`. Add an initializer:
1270
+
1271
+ ```ruby
1272
+ # config/initializers/goodmin.rb
1273
+ Goodmin.importmap.draw(Rails.root.join("config/goodmin_importmap.rb"))
1274
+ ```
1275
+
1276
+ ```ruby
1277
+ # config/goodmin_importmap.rb
1278
+ pin "controllers/my_controller"
1279
+ ```
1280
+
1281
+ The controller identifier is derived from the pin name after `controllers/`, with underscores converted to dashes — so `controllers/my_controller` becomes `data-controller="my-controller"`.
1282
+
1283
+ #### Registering a controller from an engine
1284
+
1285
+ For an engine, you must register paths during the initialization phase (not in `config/initializers/`) so that Propshaft and the importmap cache sweeper are set up in time:
1286
+
1287
+ ```ruby
1288
+ # lib/my_engine/engine.rb
1289
+ initializer "my_engine.importmap", after: "goodmin.importmap" do |app|
1290
+ app.config.assets.paths << MyEngine::Engine.root.join("app/javascript")
1291
+ Goodmin.importmap.draw MyEngine::Engine.root.join("config/goodmin_importmap.rb")
1292
+ Goodmin.importmap.cache_sweeper watches: MyEngine::Engine.root.join("app/javascript")
1293
+ end
1294
+ ```
1295
+
1296
+ ```ruby
1297
+ # config/goodmin_importmap.rb (inside your engine)
1298
+ pin_all_from MyEngine::Engine.root.join("app/javascript/controllers"),
1299
+ under: "controllers"
1300
+ ```
1301
+
1302
+ Place your controllers under `app/javascript/controllers/` within the engine:
1303
+
1304
+ ```js
1305
+ // app/javascript/controllers/my_controller.js
1306
+ import { Controller } from "@hotwired/stimulus"
1307
+
1308
+ export default class extends Controller {
1309
+ connect() {
1310
+ console.log("my engine controller connected")
1311
+ }
1312
+ }
1313
+ ```
1314
+
1315
+ ### Select boxes
1316
+
1317
+ Make a [selectize.js](http://brianreavis.github.io/selectize.js/) select box out of a text field or select box:
1318
+
1319
+ ```ruby
1320
+ f.select :authors, Author.all, {}, data: { behavior: "select-box" }
1321
+ f.text_field :tag_list, data: { behavior: "select-box" }
1322
+ ```
1323
+
1324
+ If you want to change the text that appears when an option does not exist and will be created, set the data attribute `data-add-label`.
1325
+
1326
+ ```ruby
1327
+ f.text_field :tag_list, data: { behavior: "select-box", add_label: "Create:" }
1328
+ #=> Create: foobar...
1329
+ ```
1330
+
1331
+ If the field is added post page render, it can be initialized manually:
1332
+
1333
+ ```js
1334
+ Goodmin.SelectBoxes.initializeSelectBox($el);
1335
+ ```
1336
+
1337
+ Additional options can be passed down to selectize:
1338
+
1339
+ ```js
1340
+ Goodmin.SelectBoxes.initializeSelectBox($el, {
1341
+ create: true
1342
+ });
1343
+ ```
1344
+
1345
+ ## Plugins
1346
+
1347
+ Some additional features are available as plugins:
1348
+
1349
+ - [Goodmin Uploads](https://github.com/varvet/goodmin-uploads)
1350
+ - [Goodmin Tags](https://github.com/varvet/goodmin-tags)
1351
+ - [Goodmin Redactor](https://github.com/varvet/goodmin-redactor)
1352
+
1353
+ ## Contributors
1354
+
1355
+ https://github.com/varvet/goodmin/graphs/contributors
1356
+
1357
+ ## License
1358
+
1359
+ Licensed under the MIT license. See the separate MIT-LICENSE file.