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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1359 -0
- data/Rakefile +56 -0
- data/app/assets/stylesheets/goodmin/application.css +75 -0
- data/app/controllers/goodmin/application_controller.rb +43 -0
- data/app/controllers/goodmin/resource_controller.rb +235 -0
- data/app/helpers/goodmin/application_helper.rb +45 -0
- data/app/javascript/goodmin/application.js +8 -0
- data/app/javascript/goodmin/controllers/batch_actions_controller.js +101 -0
- data/app/javascript/goodmin/controllers/datetimepicker_controller.js +24 -0
- data/app/javascript/goodmin/controllers/navigation_controller.js +30 -0
- data/app/jobs/goodmin/application_job.rb +4 -0
- data/app/mailers/goodmin/application_mailer.rb +6 -0
- data/app/models/goodmin/application_record.rb +5 -0
- data/app/views/goodmin/application/welcome.html.erb +17 -0
- data/app/views/goodmin/fields/association/_form.html.erb +19 -0
- data/app/views/goodmin/fields/association/_index.html.erb +6 -0
- data/app/views/goodmin/fields/association/_show.html.erb +6 -0
- data/app/views/goodmin/fields/boolean/_form.html.erb +7 -0
- data/app/views/goodmin/fields/boolean/_index.html.erb +1 -0
- data/app/views/goodmin/fields/boolean/_show.html.erb +1 -0
- data/app/views/goodmin/fields/date/_form.html.erb +6 -0
- data/app/views/goodmin/fields/date/_index.html.erb +1 -0
- data/app/views/goodmin/fields/date/_show.html.erb +1 -0
- data/app/views/goodmin/fields/date_time/_form.html.erb +6 -0
- data/app/views/goodmin/fields/date_time/_index.html.erb +1 -0
- data/app/views/goodmin/fields/date_time/_show.html.erb +1 -0
- data/app/views/goodmin/fields/enum/_index.html.erb +1 -0
- data/app/views/goodmin/fields/enum/_show.html.erb +1 -0
- data/app/views/goodmin/fields/nested_has_one/_form.html.erb +6 -0
- data/app/views/goodmin/fields/nested_has_one/_index.html.erb +1 -0
- data/app/views/goodmin/fields/nested_has_one/_show.html.erb +1 -0
- data/app/views/goodmin/fields/number/_form.html.erb +5 -0
- data/app/views/goodmin/fields/number/_index.html.erb +1 -0
- data/app/views/goodmin/fields/number/_show.html.erb +1 -0
- data/app/views/goodmin/fields/password/_form.html.erb +5 -0
- data/app/views/goodmin/fields/password/_index.html.erb +1 -0
- data/app/views/goodmin/fields/password/_show.html.erb +1 -0
- data/app/views/goodmin/fields/select/_form.html.erb +5 -0
- data/app/views/goodmin/fields/select/_index.html.erb +1 -0
- data/app/views/goodmin/fields/select/_show.html.erb +1 -0
- data/app/views/goodmin/fields/string/_form.html.erb +5 -0
- data/app/views/goodmin/fields/string/_index.html.erb +1 -0
- data/app/views/goodmin/fields/string/_show.html.erb +1 -0
- data/app/views/goodmin/fields/text/_form.html.erb +5 -0
- data/app/views/goodmin/fields/text/_index.html.erb +1 -0
- data/app/views/goodmin/fields/text/_show.html.erb +1 -0
- data/app/views/goodmin/resource/_actions.html.erb +9 -0
- data/app/views/goodmin/resource/_batch_actions.html.erb +12 -0
- data/app/views/goodmin/resource/_breadcrumb.html.erb +33 -0
- data/app/views/goodmin/resource/_breadcrumb_actions.html.erb +41 -0
- data/app/views/goodmin/resource/_button_actions.html.erb +3 -0
- data/app/views/goodmin/resource/_errors.html.erb +9 -0
- data/app/views/goodmin/resource/_export_actions.html.erb +15 -0
- data/app/views/goodmin/resource/_filters.html.erb +22 -0
- data/app/views/goodmin/resource/_form.html.erb +26 -0
- data/app/views/goodmin/resource/_pagination.html.erb +40 -0
- data/app/views/goodmin/resource/_scopes.html.erb +14 -0
- data/app/views/goodmin/resource/_table.html.erb +45 -0
- data/app/views/goodmin/resource/columns/_actions.html.erb +28 -0
- data/app/views/goodmin/resource/edit.html.erb +5 -0
- data/app/views/goodmin/resource/index.csv.csvbuilder +5 -0
- data/app/views/goodmin/resource/index.html.erb +10 -0
- data/app/views/goodmin/resource/index.json.jbuilder +3 -0
- data/app/views/goodmin/resource/new.html.erb +5 -0
- data/app/views/goodmin/resource/show.html.erb +11 -0
- data/app/views/goodmin/resource/show.json.jbuilder +1 -0
- data/app/views/goodmin/sessions/new.html.erb +11 -0
- data/app/views/goodmin/shared/_navigation.html.erb +0 -0
- data/app/views/goodmin/shared/_navigation_aside.html.erb +7 -0
- data/app/views/layouts/goodmin/_content.html.erb +13 -0
- data/app/views/layouts/goodmin/_layout.html.erb +22 -0
- data/app/views/layouts/goodmin/application.html.erb +28 -0
- data/app/views/layouts/goodmin/login.html.erb +18 -0
- data/config/importmap.rb +5 -0
- data/config/locales/en.yml +49 -0
- data/config/locales/pl-BR.yml +49 -0
- data/config/locales/pt-BR.yml +49 -0
- data/config/locales/sv.yml +49 -0
- data/config/routes.rb +3 -0
- data/lib/generators/goodmin/authentication/authentication_generator.rb +41 -0
- data/lib/generators/goodmin/authentication/templates/sessions_controller.rb +9 -0
- data/lib/generators/goodmin/install/install_generator.rb +41 -0
- data/lib/generators/goodmin/policy/policy_generator.rb +7 -0
- data/lib/generators/goodmin/policy/templates/policy.rb +23 -0
- data/lib/generators/goodmin/resource/resource_generator.rb +31 -0
- data/lib/generators/goodmin/resource/templates/resource.rb +25 -0
- data/lib/generators/goodmin/resource/templates/resource_controller.rb +9 -0
- data/lib/generators/goodmin/resource/templates/resource_model.rb +4 -0
- data/lib/generators/goodmin/resource/templates/resource_service.rb +23 -0
- data/lib/goodmin/authentication/sessions_controller.rb +46 -0
- data/lib/goodmin/authentication/user.rb +27 -0
- data/lib/goodmin/authentication.rb +35 -0
- data/lib/goodmin/authorization/policy.rb +41 -0
- data/lib/goodmin/authorization.rb +69 -0
- data/lib/goodmin/engine.rb +30 -0
- data/lib/goodmin/fields/association.rb +62 -0
- data/lib/goodmin/fields/base.rb +57 -0
- data/lib/goodmin/fields/boolean.rb +6 -0
- data/lib/goodmin/fields/date.rb +6 -0
- data/lib/goodmin/fields/date_time.rb +6 -0
- data/lib/goodmin/fields/enum.rb +15 -0
- data/lib/goodmin/fields/nested_has_one.rb +41 -0
- data/lib/goodmin/fields/number.rb +6 -0
- data/lib/goodmin/fields/password.rb +6 -0
- data/lib/goodmin/fields/select.rb +19 -0
- data/lib/goodmin/fields/string.rb +6 -0
- data/lib/goodmin/fields/text.rb +6 -0
- data/lib/goodmin/generators/base.rb +49 -0
- data/lib/goodmin/generators/named_base.rb +31 -0
- data/lib/goodmin/helpers/application.rb +47 -0
- data/lib/goodmin/helpers/batch_actions.rb +21 -0
- data/lib/goodmin/helpers/filters.rb +123 -0
- data/lib/goodmin/helpers/forms.rb +42 -0
- data/lib/goodmin/helpers/navigation.rb +52 -0
- data/lib/goodmin/helpers/tables.rb +25 -0
- data/lib/goodmin/helpers/translations.rb +19 -0
- data/lib/goodmin/paginator.rb +55 -0
- data/lib/goodmin/resolver.rb +141 -0
- data/lib/goodmin/resources/attribute.rb +46 -0
- data/lib/goodmin/resources/form_builder.rb +96 -0
- data/lib/goodmin/resources/form_component.rb +65 -0
- data/lib/goodmin/resources/form_components/col.rb +33 -0
- data/lib/goodmin/resources/form_components/row.rb +25 -0
- data/lib/goodmin/resources/form_components/section.rb +51 -0
- data/lib/goodmin/resources/form_components/tab.rb +49 -0
- data/lib/goodmin/resources/resource/associations.rb +23 -0
- data/lib/goodmin/resources/resource/batch_actions.rb +56 -0
- data/lib/goodmin/resources/resource/filters.rb +44 -0
- data/lib/goodmin/resources/resource/ordering.rb +41 -0
- data/lib/goodmin/resources/resource/pagination.rb +22 -0
- data/lib/goodmin/resources/resource/scopes.rb +61 -0
- data/lib/goodmin/resources/resource.rb +199 -0
- data/lib/goodmin/resources/resource_controller/batch_actions.rb +49 -0
- data/lib/goodmin/resources/resource_service/associations.rb +23 -0
- data/lib/goodmin/resources/resource_service/batch_actions.rb +52 -0
- data/lib/goodmin/resources/resource_service/filters.rb +44 -0
- data/lib/goodmin/resources/resource_service/ordering.rb +41 -0
- data/lib/goodmin/resources/resource_service/pagination.rb +22 -0
- data/lib/goodmin/resources/resource_service/scopes.rb +61 -0
- data/lib/goodmin/resources/resource_service.rb +199 -0
- data/lib/goodmin/service_locator.rb +25 -0
- data/lib/goodmin/version.rb +3 -0
- data/lib/goodmin.rb +44 -0
- data/lib/tasks/goodmin_tasks.rake +4 -0
- metadata +461 -0
data/README.md
ADDED
|
@@ -0,0 +1,1359 @@
|
|
|
1
|
+
# Goodmin
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/goodmin)
|
|
4
|
+
[](https://travis-ci.org/varvet/goodmin)
|
|
5
|
+
[](https://codeclimate.com/github/varvet/goodmin)
|
|
6
|
+
[](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
|
+

|
|
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.
|