dynamic_scaffold 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fdb3a98c44a0a9befbe60163b1f6a97f3967fa4db9319126fa0af0ab45abf83d
4
- data.tar.gz: 6f2c2dc6e2d603eac29434f034c4ea7debb06ce526cccabc270b885359baaab6
3
+ metadata.gz: 8a67e9172b0cdb76fbc08a94a0adca5d3d93066bc0437b856001a28dbadf14a5
4
+ data.tar.gz: ed1bca36c4b1035652f84cedc6c4f06fa5312d7d9adb3804dd51e439253980bb
5
5
  SHA512:
6
- metadata.gz: 6c0779201e010f34d19c7c164a722977f903a684ea8e59db2fdf21d96ac9bb31d9ddb62745a5565a2e010c394b46da9c13a3cbfa00c08bc02043336f8365f802
7
- data.tar.gz: 632c0e51372ef6f949b4efebb239adb50fcc2fc6eee614932923a526c8b93eb0b117ffae24efa766ff76901247440be5aece82ddf0af26f45cfbe898af2a5bfc
6
+ metadata.gz: 8ed6ef3d6e8e3170a9ed951318eef17dbdfaecd4e3c4a12da788b5e83f27f5b4bf70b978a6cfa9d37111c38a82c725e50387a771211db74496a1be9d82c1c118
7
+ data.tar.gz: 0f4304a394f9c44e248e4d60a3a1be090d4fd95c47c7c287921e7904cf11a3346d3b6190fe8c3ce36a4e6e6e56b9491b1411b113e5c2b46d84309d2e135bffac
data/README.md CHANGED
@@ -153,9 +153,15 @@ You can customize the list through the `DynamicScaffold::Config#list` property.
153
153
  class ShopController < ApplicationController
154
154
  include DynamicScaffold::Controller
155
155
  dynamic_scaffold Shop do |config|
156
- # You can set each title in the list through title method.
156
+ # If you want filtering that can not be handled by `config.scope`, you can use the filter method.
157
+ # Please note that returning nil will be ignored.
158
+ config.list.filter do |query|
159
+ query.where(parent_id: nil)
160
+ end
161
+
162
+ # You can set each title in the list header through title method.
157
163
  # Pass the attribute name,
158
- # config.list.title(:name)
164
+ config.list.title(:name)
159
165
  # or
160
166
  # config.list.title do |record|
161
167
  # record.name
@@ -179,7 +185,7 @@ class ShopController < ApplicationController
179
185
 
180
186
  # The first argument can also be omitted, to display item that is not model attribute.
181
187
  # The block is executed in the context of view, so you can call the method of view.
182
- config.list.item do |rec, name|
188
+ config.list.item do |rec|
183
189
  link_to "Show #{rec.name}", controls_master_shops_path
184
190
  end
185
191
  end
@@ -202,7 +208,7 @@ class ShopController < ApplicationController
202
208
  # You can use form helper methods for type,
203
209
  # text_field, check_box, radio_button, password_field, hidden_field, file_field, text_area, color_field,
204
210
  # collection_check_boxes, collection_radio_buttons, collection_select, grouped_collection_select,
205
- # time_select, date_select, datetime_select
211
+ # time_select, date_select, datetime_select, number_field, telephone_field
206
212
 
207
213
 
208
214
  # Default label is I18n model attribute name.
@@ -388,6 +394,57 @@ class UsersController < ApplicationController
388
394
  ...
389
395
  ```
390
396
 
397
+ The scope can fix value also.
398
+
399
+ ```rb
400
+ # app/controllers/users_controller.rb
401
+ class UsersController < ApplicationController
402
+ include DynamicScaffold::Controller
403
+ dynamic_scaffold User do |c|
404
+ c.scope [{role: :admin}]
405
+ # or if you use only fixed values, you can use Hash
406
+ c.scope role: :admin
407
+ ...
408
+ ```
409
+
410
+ #### Limit count
411
+
412
+ You can specify the maximum count of registrations.
413
+
414
+ ```rb
415
+ # app/controllers/users_controller.rb
416
+ class UsersController < ApplicationController
417
+ include DynamicScaffold::Controller
418
+ dynamic_scaffold User do |c|
419
+ c.max_count 10
420
+ ...
421
+ ```
422
+
423
+ If database support lock, you can lock the table before count.
424
+
425
+ ```rb
426
+ # app/controllers/users_controller.rb
427
+ class UsersController < ApplicationController
428
+ include DynamicScaffold::Controller
429
+ dynamic_scaffold User do |c|
430
+ c.max_count 10, lock: true
431
+ ...
432
+ ```
433
+
434
+ If you want a finer lock control, you can use the block.
435
+
436
+ ```rb
437
+ # app/controllers/users_controller.rb
438
+ class UsersController < ApplicationController
439
+ include DynamicScaffold::Controller
440
+ dynamic_scaffold User do |c|
441
+ c.max_count 10 do |record|
442
+ ActiveRecord::Base.connection.execute("...")
443
+ end
444
+ ...
445
+ ```
446
+
447
+ Please note that the count of records is affected by scope and list.filter.
391
448
 
392
449
  ### View helper
393
450
 
@@ -476,43 +533,6 @@ class ShopController < ApplicationController
476
533
  <%= dynamic_scaffold.vars.shop_type.name %>
477
534
  ```
478
535
 
479
- ### Password Handling Tips
480
-
481
- Passwords are not displayed on the editing screen and should be skipped validation when sent with empty.
482
-
483
- You can do it by preparing virtual attributes for the edit action, and switching between edit and new action.
484
-
485
- ```rb
486
- class User < ApplicationRecord
487
- validates :password, presence: true
488
-
489
- attr_reader :password_edit
490
-
491
- def password_edit=(value)
492
- @password_edit = value
493
- self.password = value if value.present?
494
- end
495
- end
496
- ```
497
-
498
- ```rb
499
- class UsersController < ScaffoldController
500
- include DynamicScaffold::Controller
501
- dynamic_scaffold User do |config|
502
-
503
- # If the block given to the `if` method returns false, the element is ignored.
504
- # The block argument is `params` in controller.
505
- config.form.item(:password_field, :password)
506
- .if {|p| %w[new create].include? p[:action] }
507
-
508
- # When you call the `proxy` method, the element's error messages and label will be used for the specified attribute.
509
- config.form.item(:password_field, :password_edit)
510
- .proxy(:password)
511
- .if {|p| %w[edit update].include? p[:action] }
512
- end
513
- end
514
- ```
515
-
516
536
 
517
537
  ## Contributing
518
538
 
@@ -60,23 +60,23 @@ window.DynamicScaffold.createElement = function(tagName, attributes, style, inne
60
60
  //confirm
61
61
  ;(function(){
62
62
  window.DynamicScaffold.confirm = function(options){
63
- const exists = document.querySelector('.dynamicScaffold-overlay')
63
+ const exists = document.querySelector('.ds-overlay')
64
64
  if(exists){
65
65
  return
66
66
  }
67
67
 
68
- const overlay = DynamicScaffold.createElement('div', {class: 'dynamicScaffold-overlay'})
69
- const confirm = DynamicScaffold.createElement('div', {class: 'dynamicScaffold-confirm'})
70
- const inner = DynamicScaffold.createElement('div', {class: 'dynamicScaffold-confirm-inner'})
68
+ const overlay = DynamicScaffold.createElement('div', {class: 'ds-overlay'})
69
+ const confirm = DynamicScaffold.createElement('div', {class: 'ds-confirm'})
70
+ const inner = DynamicScaffold.createElement('div', {class: 'ds-confirm-inner'})
71
71
  overlay.appendChild(confirm)
72
72
  confirm.appendChild(inner)
73
73
 
74
- const message = DynamicScaffold.createElement('div', {class: 'dynamicScaffold-confirm-message'}, null, options.message)
74
+ const message = DynamicScaffold.createElement('div', {class: 'ds-confirm-message'}, null, options.message)
75
75
  inner.appendChild(message)
76
76
 
77
77
  const ok = DynamicScaffold.createElement('button', {class: options.ok.class}, {}, options.ok.text)
78
78
  const cancel = DynamicScaffold.createElement('button', {class: options.cancel.class}, {}, options.cancel.text)
79
- const buttons = DynamicScaffold.createElement('div', {class: 'dynamicScaffold-confirm-buttons'})
79
+ const buttons = DynamicScaffold.createElement('div', {class: 'ds-confirm-buttons'})
80
80
  buttons.appendChild(cancel)
81
81
  buttons.appendChild(ok)
82
82
  inner.appendChild(buttons)
@@ -26,15 +26,15 @@ document.addEventListener('dynamic_scaffold:load', function (){
26
26
  form.submit()
27
27
  }
28
28
 
29
- const buttons = document.querySelectorAll('.dynamicScaffoldJs-destory')
29
+ const buttons = document.querySelectorAll('.js-ds-destory')
30
30
  if(buttons.length === 0) return
31
31
 
32
- const wrapper = buttons[0].closest('.dynamicScaffoldJs-item-wrapper')
32
+ const wrapper = buttons[0].closest('.js-ds-item-wrapper')
33
33
  Array.prototype.forEach.call(buttons, function(button){
34
- const row = button.closest('.dynamicScaffoldJs-item-row')
34
+ const row = button.closest('.js-ds-list-row')
35
35
  button.addEventListener('click', function(e){
36
36
  e.preventDefault()
37
- row.classList.add('dynamicScaffold-destorying')
37
+ row.classList.add('ds-destorying')
38
38
  DynamicScaffold.confirm({
39
39
  message: button.getAttribute('data-confirm-message'),
40
40
  ok: {
@@ -48,7 +48,7 @@ document.addEventListener('dynamic_scaffold:load', function (){
48
48
  text: wrapper.getAttribute('data-confirm-cancel'),
49
49
  class: wrapper.getAttribute('data-confirm-cancel-class'),
50
50
  action: function(){
51
- row.classList.remove("dynamicScaffold-destorying")
51
+ row.classList.remove("ds-destorying")
52
52
  }
53
53
  }
54
54
  })
@@ -1,9 +1,9 @@
1
1
  document.addEventListener('dynamic_scaffold:load', function (){
2
- const inputs = document.querySelectorAll('.dynamicScaffoldJs-image')
2
+ const inputs = document.querySelectorAll('.js-ds-image')
3
3
  Array.prototype.forEach.call(inputs, function(input){
4
4
  // initialize
5
- const wrapper = input.closest('.dynamicScaffoldJs-image-wrapper')
6
- const preview = wrapper.querySelector('.dynamicScaffoldJs-image-preview')
5
+ const wrapper = input.closest('.js-ds-image-wrapper')
6
+ const preview = wrapper.querySelector('.js-ds-image-preview')
7
7
  const img = preview.querySelector('img')
8
8
 
9
9
  // init preview display
@@ -15,11 +15,11 @@ document.addEventListener('dynamic_scaffold:load', function (){
15
15
  }
16
16
 
17
17
  // delete event
18
- const button = preview.querySelector('.dynamicScaffoldJs-image-remove')
18
+ const button = preview.querySelector('.js-ds-image-remove')
19
19
  let flag
20
20
  if(button)
21
21
  {
22
- flag = wrapper.querySelector('.dynamicScaffoldJs-image-remove-flag')
22
+ flag = wrapper.querySelector('.js-ds-image-remove-flag')
23
23
  button.addEventListener('click', function(e){
24
24
  preview.style.display = 'none'
25
25
  flag.disabled = false
@@ -2,7 +2,7 @@ document.addEventListener('dynamic_scaffold:load', function(){
2
2
  function handlePagination(pagination){
3
3
  const itemCount = pagination.children.lenth
4
4
  const items = Array.prototype.filter.call(pagination.children, function(li){
5
- return li.classList.contains('dynamicScaffoldJs-page-item')
5
+ return li.classList.contains('js-ds-page-item')
6
6
  })
7
7
 
8
8
  const currentIndex = items.findIndex(function(li){
@@ -20,7 +20,7 @@ document.addEventListener('dynamic_scaffold:load', function(){
20
20
  })
21
21
  }
22
22
 
23
- Array.prototype.forEach.call(document.querySelectorAll('.dynamicScaffoldJs-pagination'), function(pagination){
23
+ Array.prototype.forEach.call(document.querySelectorAll('.js-ds-pagination'), function(pagination){
24
24
  handlePagination(pagination)
25
25
  })
26
26
  })
@@ -41,7 +41,7 @@ document.addEventListener('dynamic_scaffold:load', function(){
41
41
  // Ignore while animating
42
42
  if(promises.length) return
43
43
 
44
- const source = button.closest('.dynamicScaffoldJs-item-row')
44
+ const source = button.closest('.js-ds-list-row')
45
45
  source.style.position = 'relative'
46
46
  source.style.zIndex = 1000
47
47
 
@@ -93,33 +93,33 @@ document.addEventListener('dynamic_scaffold:load', function(){
93
93
  }
94
94
 
95
95
  // Register `transitionend` event in all the rows.
96
- Array.prototype.forEach.call(document.querySelectorAll('.dynamicScaffoldJs-item-row'), function(row){
96
+ Array.prototype.forEach.call(document.querySelectorAll('.js-ds-list-row'), function(row){
97
97
  row.addEventListener('transitionend', function(e){
98
98
  if(e.target == row) row.dynamicScaffoldSortingResolver(row)
99
99
  })
100
100
  })
101
101
 
102
102
  // Register events on each button.
103
- addClickEvent(document.querySelectorAll('.dynamicScaffoldJs-sorter-top'), function(source){
104
- return document.querySelector('.dynamicScaffoldJs-item-row:first-child')
103
+ addClickEvent(document.querySelectorAll('.js-ds-sorter-top'), function(source){
104
+ return document.querySelector('.js-ds-list-row:first-child')
105
105
  }, otherSideAnimationForUp, function(source, target){
106
106
  source.parentNode.insertBefore(source, target)
107
107
  })
108
108
 
109
- addClickEvent(document.querySelectorAll('.dynamicScaffoldJs-sorter-up'), function(source){
109
+ addClickEvent(document.querySelectorAll('.js-ds-sorter-up'), function(source){
110
110
  return source.previousElementSibling
111
111
  }, otherSideAnimationForUp, function(source, target){
112
112
  source.parentNode.insertBefore(source, target)
113
113
  })
114
114
 
115
- addClickEvent(document.querySelectorAll('.dynamicScaffoldJs-sorter-down'), function(source){
115
+ addClickEvent(document.querySelectorAll('.js-ds-sorter-down'), function(source){
116
116
  return source.nextElementSibling
117
117
  }, otherSideAnimationForDown, function(source, target){
118
118
  source.parentNode.insertBefore(target, source)
119
119
  })
120
120
 
121
- addClickEvent(document.querySelectorAll('.dynamicScaffoldJs-sorter-bottom'), function(source){
122
- return document.querySelector('.dynamicScaffoldJs-item-row:last-child')
121
+ addClickEvent(document.querySelectorAll('.js-ds-sorter-bottom'), function(source){
122
+ return document.querySelector('.js-ds-list-row:last-child')
123
123
  }, otherSideAnimationForDown, function(source, target){
124
124
  source.parentNode.insertBefore(source, null)
125
125
  })
@@ -48,4 +48,8 @@ $dynamic_scaffold_danger: #dc3545 !default;
48
48
 
49
49
  .page-item.disabled .page-link path{
50
50
  fill: $dynamic_scaffold_secondary;
51
+ }
52
+
53
+ .btn-outline-primary.disabled, .btn-outline-primary:disabled{
54
+ background-color: transparent;
51
55
  }
@@ -1,22 +1,22 @@
1
- .dynamicScaffold-row{
1
+ .ds-row{
2
2
  margin-bottom: 10px
3
3
  }
4
4
 
5
- .dynamicScaffold-error-message{
5
+ .ds-error-message{
6
6
  color: #dc3545;
7
7
 
8
8
  }
9
9
 
10
- .dynamicScaffold-error-message path{
10
+ .ds-error-message path{
11
11
  fill: #dc3545;
12
12
  }
13
13
 
14
- .dynamicScaffold-destorying{
14
+ .ds-destorying{
15
15
  background-color: #fdd8df !important;
16
16
  opacity: 0.6;
17
17
  }
18
18
 
19
- .dynamicScaffold-svg-icon{
19
+ .ds-svg-icon{
20
20
  width: 15px;
21
21
  height: 15px;
22
22
  vertical-align: baseline;
@@ -88,7 +88,7 @@
88
88
  fill: #fff;
89
89
  }
90
90
 
91
- .dynamicScaffold-overlay{
91
+ .ds-overlay{
92
92
  position: fixed;
93
93
  top: 0;
94
94
  left: 0;
@@ -98,13 +98,13 @@
98
98
  background-color: rgba(0,0,0,0.2);
99
99
  }
100
100
 
101
- .dynamicScaffold-confirm{
101
+ .ds-confirm{
102
102
  position: fixed;
103
103
  top: 40px;
104
104
  width: 100%;
105
105
  }
106
106
 
107
- .dynamicScaffold-confirm-inner{
107
+ .ds-confirm-inner{
108
108
  background-color: #fff;
109
109
  border-radius: 10px;
110
110
  box-shadow: 3px 3px 3px rgba(0,0,0,0.3);
@@ -116,16 +116,16 @@
116
116
  margin: 0 auto;
117
117
  }
118
118
 
119
- .dynamicScaffold-confirm-message{
119
+ .ds-confirm-message{
120
120
  height: 68px;
121
121
  font-size: 16px;
122
122
  }
123
123
 
124
- .dynamicScaffold-confirm-buttons{
124
+ .ds-confirm-buttons{
125
125
  text-align: right;
126
126
  }
127
127
 
128
- .dynamicScaffold-confirm-buttons .btn{
128
+ .ds-confirm-buttons .btn{
129
129
  margin-left: 10px;
130
130
  }
131
131
 
@@ -1,30 +1,30 @@
1
1
  @charset "UTF-8";
2
- /*<ul id="access-list" class="resplist resplist-striped">
3
- <li class="resplist-row">
4
- <div class="resplist-heading">Some Title</div>
5
- <div class="resplist-items">
6
- <div class="resplist-item resplist-item-xs">
7
- <div class="resplist-label">ID</div>
8
- <div class="resplist-value">1</div>
2
+ /*<ul id="access-list" class="ds-list ds-list-striped">
3
+ <li class="ds-list-row">
4
+ <div class="ds-list-heading">Some Title</div>
5
+ <div class="ds-list-items">
6
+ <div class="ds-list-item ds-list-item-xs">
7
+ <div class="ds-list-label">ID</div>
8
+ <div class="ds-list-value">1</div>
9
9
  </div>
10
- <div class="resplist-item resplist-item-md">
11
- <div class="resplist-label">Name</div>
12
- <div class="resplist-value">Micheal Jackson</div>
10
+ <div class="ds-list-item ds-list-item-md">
11
+ <div class="ds-list-label">Name</div>
12
+ <div class="ds-list-value">Micheal Jackson</div>
13
13
  </div>
14
- <div class="resplist-item resplist-item-md">
15
- <div class="resplist-label">Born</div>
16
- <div class="resplist-value">1958/8/29</div>
14
+ <div class="ds-list-item ds-list-item-md">
15
+ <div class="ds-list-label">Born</div>
16
+ <div class="ds-list-value">1958/8/29</div>
17
17
  </div>
18
- <div class="resplist-item resplist-item-md">
19
- <div class="resplist-label">Died</div>
20
- <div class="resplist-value">2009/6/25</div>
18
+ <div class="ds-list-item ds-list-item-md">
19
+ <div class="ds-list-label">Died</div>
20
+ <div class="ds-list-value">2009/6/25</div>
21
21
  </div>
22
- <div class="resplist-item resplist-item-md">
23
- <div class="resplist-label">Occupations</div>
24
- <div class="resplist-value">Musician, singer-songwriter, arranger, dancer, entertainer, choreographer, actor, businessman, philanthropist</div>
22
+ <div class="ds-list-item ds-list-item-md">
23
+ <div class="ds-list-label">Occupations</div>
24
+ <div class="ds-list-value">Musician, singer-songwriter, arranger, dancer, entertainer, choreographer, actor, businessman, philanthropist</div>
25
25
  </div>
26
- <div class="resplist-item pull-right">
27
- <div class="resplist-value">
26
+ <div class="ds-list-item pull-right">
27
+ <div class="ds-list-value">
28
28
  <div class="btn-group">
29
29
  <a href="#" class="btn btn-primary">編集</a>
30
30
  </div>
@@ -38,18 +38,18 @@
38
38
  </div>
39
39
  </div>
40
40
  </div>
41
- <div class="resplist-footer">Some Footer</div>
41
+ <div class="ds-list-footer">Some Footer</div>
42
42
  </li>
43
43
  </ul>*/
44
- .resplist-items:before, .resplist-item:before, .resplist-row:before, .resplist-items:after, .resplist-item:after, .resplist-row:after {
44
+ .ds-list-items:before, .ds-list-item:before, .ds-list-row:before, .ds-list-items:after, .ds-list-item:after, .ds-list-row:after {
45
45
  content: " ";
46
46
  display: table;
47
47
  }
48
- .resplist-items:after, .resplist-item:after, .resplist-row:after {
48
+ .ds-list-items:after, .ds-list-item:after, .ds-list-row:after {
49
49
  clear: both;
50
50
  }
51
51
 
52
- .resplist {
52
+ .ds-list {
53
53
  list-style: none;
54
54
  padding: 0;
55
55
  border-collapse: separate;
@@ -57,11 +57,11 @@
57
57
  flex-wrap: wrap;
58
58
  }
59
59
 
60
- .resplist-striped .resplist-row:nth-child(odd) {
60
+ .ds-list-striped .ds-list-row:nth-child(odd) {
61
61
  background-color: #f9f9f9;
62
62
  }
63
63
 
64
- .resplist-heading {
64
+ .ds-list-heading {
65
65
  font-weight: bold;
66
66
  padding: 8px 20px;
67
67
  overflow: hidden;
@@ -70,11 +70,11 @@
70
70
  max-width: 100%;
71
71
  }
72
72
 
73
- .resplist-footer {
73
+ .ds-list-footer {
74
74
  padding: 8px 20px;
75
75
  }
76
76
 
77
- .resplist-label {
77
+ .ds-list-label {
78
78
  overflow: hidden;
79
79
  white-space: nowrap;
80
80
  font-size: 11px;
@@ -85,11 +85,11 @@
85
85
  line-height: 15px;
86
86
  }
87
87
 
88
- .resplist-label-hidden {
88
+ .ds-list-label-hidden {
89
89
  background-color: transparent;
90
90
  }
91
91
 
92
- .resplist-value {
92
+ .ds-list-value {
93
93
  overflow: hidden;
94
94
  white-space: nowrap;
95
95
  font-size: 14px;
@@ -99,12 +99,12 @@
99
99
  vertical-align: middle;
100
100
  }
101
101
 
102
- .resplist-label + .resplist-value {
102
+ .ds-list-label + .ds-list-value {
103
103
  height: 22px;
104
104
  line-height: 22px;
105
105
  }
106
106
 
107
- .resplist-item {
107
+ .ds-list-item {
108
108
  float: left;
109
109
  max-width: 100%;
110
110
  margin: 8px 0;
@@ -112,47 +112,47 @@
112
112
  overflow: hidden;
113
113
  }
114
114
 
115
- .resplist-row {
115
+ .ds-list-row {
116
116
  background-color: #fff;
117
117
  border-top: 1px solid #dddddd;
118
118
  padding: 0;
119
119
  width: 100%;
120
120
  }
121
121
 
122
- .resplist-item-xs {
122
+ .ds-list-item-xs {
123
123
  width: 60px;
124
124
  }
125
125
 
126
- .resplist-item-sm {
126
+ .ds-list-item-sm {
127
127
  width: 120px;
128
128
  }
129
129
 
130
- .resplist-item-md {
130
+ .ds-list-item-md {
131
131
  width: 180px;
132
132
  }
133
133
 
134
- .resplist-item-lg {
134
+ .ds-list-item-lg {
135
135
  width: 240px;
136
136
  }
137
137
 
138
138
  @media (max-width: 768px) {
139
- .resplist-heading {
139
+ .ds-list-heading {
140
140
  padding: 8px 12px;
141
141
  }
142
142
 
143
- .resplist-footer {
143
+ .ds-list-footer {
144
144
  padding: 8px 12px;
145
145
  }
146
146
 
147
- .resplist-item {
147
+ .ds-list-item {
148
148
  margin: 4px 0;
149
149
  }
150
150
 
151
- .resplist-label {
151
+ .ds-list-label {
152
152
  padding: 0 12px;
153
153
  }
154
154
 
155
- .resplist-value {
155
+ .ds-list-value {
156
156
  padding: 0 12px;
157
157
  }
158
158
  }
@@ -29,11 +29,11 @@
29
29
  <%end%>
30
30
  <% elsif elem.type? :carrierwave_image %>
31
31
  <%- image = form.object.public_send(elem.name) -%>
32
- <div class="dynamicScaffoldJs-image-wrapper">
33
- <div class="dynamicScaffoldJs-image-preview panel panel-default card mb-1">
32
+ <div class="js-ds-image-wrapper">
33
+ <div class="js-ds-image-preview panel panel-default card mb-1">
34
34
  <% if elem.options[:removable] %>
35
35
  <div class="text-right panel-heading card-header">
36
- <button type="button" class="btn btn-outline-danger btn-danger btn-sm dynamicScaffoldJs-image-remove">
36
+ <button type="button" class="btn btn-outline-danger btn-danger btn-sm js-ds-image-remove">
37
37
  <%= dynamic_scaffold_icon :times %>
38
38
  </button>
39
39
  </div>
@@ -44,8 +44,8 @@
44
44
  </div>
45
45
  </div>
46
46
  </div>
47
- <%= form.hidden_field "remove_#{elem.name}", value: "1", disabled:"disabled", class: 'dynamicScaffoldJs-image-remove-flag' if elem.options[:removable]%>
48
- <%= elem.render(self, form, 'form-control-file dynamicScaffoldJs-image') do |attr|%>
47
+ <%= form.hidden_field "remove_#{elem.name}", value: "1", disabled:"disabled", class: 'js-ds-image-remove-flag' if elem.options[:removable]%>
48
+ <%= elem.render(self, form, 'form-control-file js-ds-image') do |attr|%>
49
49
  <%= form.file_field(elem.name, attr) %>
50
50
  <%end%>
51
51
  <%= form.hidden_field "#{elem.name}_cache" %>
@@ -58,7 +58,7 @@
58
58
  <%-end-%>
59
59
  </div>
60
60
  <%- if errors.present? -%>
61
- <ul class="list-unstyled dynamicScaffold-error-message">
61
+ <ul class="list-unstyled ds-error-message">
62
62
  <% errors.each do |err|%>
63
63
  <li><%= dynamic_scaffold_icon :error %> <%= err %></li>
64
64
  <%end%>
@@ -5,43 +5,48 @@
5
5
  <%end%>
6
6
  <input type="hidden" class="authenticity_param_name" value="<%= request_forgery_protection_token %>">
7
7
  <%= form_with method: :patch, url: dynamic_scaffold_path(:sort, request_queries(dynamic_scaffold.list.page_param_name)), local: true do%>
8
- <div class="dynamicScaffold-row">
9
- <%= link_to dynamic_scaffold_path(:new, request_queries), class: 'btn btn-outline-primary btn-primary btn-sm' do%>
8
+ <div class="ds-row">
9
+ <%= link_to dynamic_scaffold_path(:new, request_queries), class: class_names('btn btn-outline-primary btn-primary btn-sm spec-ds-add', 'disabled': dynamic_scaffold.max_count?(@count)) do%>
10
10
  <%= dynamic_scaffold_icon(:add) %> <%= t('dynamic_scaffold.button.add') %>
11
+ <%- unless dynamic_scaffold.max_count.nil? -%>
12
+ &nbsp;<span class="badge badge-light">
13
+ <%= @count %>&nbsp;/&nbsp;<%= dynamic_scaffold.max_count%>
14
+ </span>
15
+ <% end %>
11
16
  <%end%>
12
17
  </div>
13
18
  <%= render 'dynamic_scaffold/bootstrap/pagination' %>
14
- <div class="dynamicScaffold-row">
19
+ <div class="ds-row">
15
20
  <%= render 'dynamic_scaffold/bootstrap/save_order'%>
16
21
  </div>
17
- <div class="dynamicScaffold-row">
22
+ <div class="ds-row">
18
23
  <% if @records.empty? %>
19
24
  <p class="lead"><%= t('dynamic_scaffold.message.no_records', model: dynamic_scaffold.title.name) %></p>
20
25
  <% else %>
21
26
  <ul
22
- class="resplist resplist-striped dynamicScaffoldJs-item-wrapper"
27
+ class="ds-list ds-list-striped js-ds-item-wrapper"
23
28
  data-confirm-ok="<%= t('dynamic_scaffold.message.confirm_ok') %>"
24
29
  data-confirm-ok-class="btn btn-danger btn-sm"
25
30
  data-confirm-cancel="<%= t('dynamic_scaffold.message.confirm_cancel') %>"
26
31
  data-confirm-cancel-class="btn btn-outline-secondary btn-default btn-sm"
27
32
  >
28
33
  <%@records.each do |record|%>
29
- <li class="resplist-row dynamicScaffoldJs-item-row">
34
+ <li class="ds-list-row js-ds-list-row">
30
35
  <% if dynamic_scaffold.list.title? %>
31
- <div class="resplist-heading"><%= dynamic_scaffold.list.title(record) %></div>
36
+ <div class="ds-list-heading"><%= dynamic_scaffold.list.title(record) %></div>
32
37
  <% end %>
33
- <div class="resplist-items">
38
+ <div class="ds-list-items">
34
39
  <%dynamic_scaffold.list.items.each do |disp|%>
35
- <%= content_tag :div, class: class_names('resplist-item', disp.classnames), **disp.html_attributes do%>
36
- <div class="resplist-label"><%= disp.label %></div>
37
- <div class="resplist-value"><%= disp.value self, record %></div>
40
+ <%= content_tag :div, class: class_names('ds-list-item', disp.classnames), **disp.html_attributes do%>
41
+ <div class="ds-list-label"><%= disp.label %></div>
42
+ <div class="ds-list-value"><%= disp.value self, record %></div>
38
43
  <%end%>
39
44
  <%end%>
40
45
  </div>
41
- <div class="resplist-footer clearfix">
46
+ <div class="ds-list-footer clearfix">
42
47
  <div class="float-right pull-right">
43
48
  <div class="btn-group">
44
- <%= link_to dynamic_scaffold_path(:edit, request_queries.merge(id: record[record.class.primary_key])), class: 'btn btn-primary btn-outline-primary btn-sm edit' do %>
49
+ <%= link_to dynamic_scaffold_path(:edit, request_queries.merge(id: record[record.class.primary_key])), class: 'btn btn-primary btn-outline-primary btn-sm spec-ds-edit' do %>
45
50
  <%= dynamic_scaffold_icon(:edit) %> <%= t('dynamic_scaffold.button.edit') %>
46
51
  <%end%>
47
52
  </div>
@@ -50,16 +55,16 @@
50
55
  <input type="hidden" name="pkeys[][<%=pkey%>]" value="<%= record[pkey] %>">
51
56
  <%end%>
52
57
  <div class="btn-group">
53
- <button class="btn btn-outline-secondary btn-default btn-sm dynamicScaffoldJs-sorter-top">
58
+ <button class="btn btn-outline-secondary btn-default btn-sm js-ds-sorter-top">
54
59
  <%= dynamic_scaffold_icon(:top) %>
55
60
  </button>
56
- <button class="btn btn-outline-secondary btn-default btn-sm dynamicScaffoldJs-sorter-up">
61
+ <button class="btn btn-outline-secondary btn-default btn-sm js-ds-sorter-up">
57
62
  <%= dynamic_scaffold_icon(:up) %>
58
63
  </button>
59
- <button class="btn btn-outline-secondary btn-default btn-sm dynamicScaffoldJs-sorter-down">
64
+ <button class="btn btn-outline-secondary btn-default btn-sm js-ds-sorter-down">
60
65
  <%= dynamic_scaffold_icon(:down) %>
61
66
  </button>
62
- <button class="btn btn-outline-secondary btn-default btn-sm dynamicScaffoldJs-sorter-bottom">
67
+ <button class="btn btn-outline-secondary btn-default btn-sm js-ds-sorter-bottom">
63
68
  <%= dynamic_scaffold_icon(:bottom) %>
64
69
  </button>
65
70
  </div>
@@ -68,7 +73,7 @@
68
73
  <button
69
74
  data-action="<%= dynamic_scaffold_path(:update, request_queries(dynamic_scaffold.list.page_param_name).merge(id: record[record.class.primary_key])) %>"
70
75
  data-confirm-message="<%= t('dynamic_scaffold.message.destroy_confirm') %>"
71
- class="btn btn-danger btn-sm dynamicScaffoldJs-destory"
76
+ class="btn btn-danger btn-sm js-ds-destory"
72
77
  >
73
78
  <%= dynamic_scaffold_icon(:delete) %>
74
79
  </button>
@@ -80,7 +85,7 @@
80
85
  </ul>
81
86
  <% end %>
82
87
  </div>
83
- <div class="dynamicScaffold-row">
88
+ <div class="ds-row">
84
89
  <%= render 'dynamic_scaffold/bootstrap/save_order'%>
85
90
  </div>
86
91
  <%= render 'dynamic_scaffold/bootstrap/pagination' %>
@@ -1,5 +1,5 @@
1
1
  <%if dynamic_scaffold.list.pagination %>
2
- <div class="dynamicScaffold-row">
2
+ <div class="ds-row">
3
3
  <%= paginate @records, {views_prefix: 'dynamic_scaffold/bootstrap'}.merge(dynamic_scaffold.list.pagination.kaminari_options) %>
4
4
  </div>
5
5
  <%end%>
@@ -1,3 +1,3 @@
1
- <li class='page-item disabled gap dynamicScaffoldJs-page-item'>
1
+ <li class='page-item disabled gap js-ds-page-item'>
2
2
  <%= link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link' %>
3
3
  </li>
@@ -1,9 +1,9 @@
1
1
  <% if page.current? %>
2
- <li class="<%= class_names 'page-item current dynamicScaffoldJs-page-item', {'active': dynamic_scaffold.list.pagination.highlight_current} %>">
2
+ <li class="<%= class_names 'page-item current js-ds-page-item', {'active': dynamic_scaffold.list.pagination.highlight_current} %>">
3
3
  <%= content_tag :a, dynamic_scaffold.list.pagination.page_number(page, @records), remote: remote, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)), class: 'page-link' %>
4
4
  </li>
5
5
  <% else %>
6
- <li class="<%= class_names('page-item dynamicScaffoldJs-page-item', dynamic_scaffold.list.pagination.page_class(page, @records)) %>">
6
+ <li class="<%= class_names('page-item js-ds-page-item', dynamic_scaffold.list.pagination.page_class(page, @records)) %>">
7
7
  <%= link_to page, url, remote: remote, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)), class: 'page-link' %>
8
8
  </li>
9
9
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <%= paginator.render do %>
2
2
  <nav class="text-center text-xs-center">
3
- <ul class="pagination justify-content-center dynamicScaffoldJs-pagination">
3
+ <ul class="pagination justify-content-center js-ds-pagination">
4
4
  <%= first_page_tag if dynamic_scaffold.list.pagination.end_buttons %>
5
5
  <%= prev_page_tag if dynamic_scaffold.list.pagination.neighbor_buttons %>
6
6
  <% each_page do |page| %>
@@ -119,7 +119,7 @@ module DynamicScaffold
119
119
  end
120
120
 
121
121
  class Config
122
- attr_reader :model, :form, :list, :title, :controller
122
+ attr_reader :model, :form, :list, :title, :controller, :lock_before_count, :max_count_options
123
123
  def initialize(model, controller)
124
124
  @model = model
125
125
  @controller = controller
@@ -127,6 +127,7 @@ module DynamicScaffold
127
127
  @list = ListBuilder.new(self)
128
128
  @title = Title.new(self)
129
129
  @vars = Vars.new(self)
130
+ @max_count_options = {}
130
131
  end
131
132
 
132
133
  def vars(name = nil, &block)
@@ -138,10 +139,22 @@ module DynamicScaffold
138
139
  end
139
140
  end
140
141
 
141
- def scope(parameter_names = nil)
142
- @scope = parameter_names unless parameter_names.nil?
142
+ def scope(scopes = nil)
143
+ @scope = scopes unless scopes.nil?
143
144
  @scope
144
145
  end
146
+
147
+ def max_count(count = nil, options = nil, &block)
148
+ @max_count = count unless count.nil?
149
+ @max_count_options = options unless options.nil?
150
+ @lock_before_count = block if block_given?
151
+ @max_count
152
+ end
153
+
154
+ def max_count?(count)
155
+ return false if max_count.nil?
156
+ count >= max_count
157
+ end
145
158
  end
146
159
 
147
160
  class ListBuilder
@@ -151,6 +164,7 @@ module DynamicScaffold
151
164
  @sorter = nil
152
165
  @order = []
153
166
  @title = nil
167
+ @filter = nil
154
168
  end
155
169
 
156
170
  def pagination(options = nil)
@@ -212,6 +226,25 @@ module DynamicScaffold
212
226
  def title?
213
227
  @title.present?
214
228
  end
229
+
230
+ def build_sql(scope_params)
231
+ sql = @config.model.all
232
+ sql = sql.where scope_params
233
+ ret = @config.controller.instance_exec(sql, &@filter) unless @filter.nil?
234
+ sql = ret unless ret.nil?
235
+ unless sql.is_a? ::ActiveRecord::Relation
236
+ raise(
237
+ Error::InvalidOperation,
238
+ 'You must return ActiveRecord::Relation from filter block'
239
+ )
240
+ end
241
+ sql
242
+ end
243
+
244
+ def filter(&block)
245
+ @filter = block if block_given?
246
+ @filter
247
+ end
215
248
  end
216
249
 
217
250
  class FormBuilder
@@ -251,7 +284,9 @@ module DynamicScaffold
251
284
  :password_field,
252
285
  :hidden_field,
253
286
  :file_field,
254
- :color_field then
287
+ :color_field,
288
+ :number_field,
289
+ :telephone_field then
255
290
  item = Form::Item::SingleOption.new(@config, type, *args)
256
291
  when
257
292
  :time_select,
@@ -24,25 +24,30 @@ module DynamicScaffold
24
24
  # Actions
25
25
 
26
26
  def index # rubocop:disable Metrics/AbcSize
27
- @records = dynamic_scaffold.model.all
28
- raise Error::InvalidOperation, 'You must return ActiveRecord::Relation' unless @records.is_a? ::ActiveRecord::Relation
29
-
30
- if dynamic_scaffold.list.pagination
31
- @records = @records
32
- .page(params[dynamic_scaffold.list.pagination.param_name])
33
- .per(dynamic_scaffold.list.pagination.per_page)
34
- end
35
-
36
- @records = @records.where scope_params
27
+ @records = dynamic_scaffold.list.build_sql(scope_params)
28
+ @count = @records.count unless dynamic_scaffold.max_count.nil?
29
+ @records = handle_pagination(@records)
37
30
  @records = @records.order dynamic_scaffold.list.sorter if dynamic_scaffold.list.sorter
38
31
  @records = @records.order(*dynamic_scaffold.list.order) unless dynamic_scaffold.list.order.empty?
39
32
 
40
- @records = yield(@records) if block_given?
41
- raise Error::InvalidOperation, 'You must return ActiveRecord::Relation' if @records.nil?
33
+ ret = yield(@records) if block_given?
34
+ @records = ret unless ret.nil?
35
+ unless @records.is_a? ::ActiveRecord::Relation
36
+ raise(
37
+ Error::InvalidOperation,
38
+ 'You must return ActiveRecord::Relation from super block'
39
+ )
40
+ end
41
+
42
42
  @records
43
43
  end
44
44
 
45
- def new
45
+ def new # rubocop:disable Metrics/AbcSize
46
+ unless dynamic_scaffold.max_count.nil?
47
+ count = dynamic_scaffold.list.build_sql(scope_params).count
48
+ raise Error::InvalidOperation, 'You can not add any more.' if dynamic_scaffold.max_count?(count)
49
+ end
50
+
46
51
  @record = dynamic_scaffold.model.new
47
52
 
48
53
  defaults = dynamic_scaffold.form.items.each_with_object({}) do |item, memo|
@@ -61,6 +66,7 @@ module DynamicScaffold
61
66
  @record.attributes = update_values
62
67
  bind_sorter_value(@record) if dynamic_scaffold.list.sorter
63
68
  dynamic_scaffold.model.transaction do
69
+ check_max_count!
64
70
  yield(@record) if block_given?
65
71
  if @record.save
66
72
  redirect_to dynamic_scaffold_path(:index, request_queries)
@@ -94,7 +100,7 @@ module DynamicScaffold
94
100
  end
95
101
  rescue ::ActiveRecord::InvalidForeignKey => _error
96
102
  flash[:dynamic_scaffold_danger] = I18n.t('dynamic_scaffold.alert.destroy.invalid_foreign_key')
97
- rescue => error
103
+ rescue StandardError => error
98
104
  flash[:dynamic_scaffold_danger] = I18n.t('dynamic_scaffold.alert.destroy.failed')
99
105
  logger.error(error)
100
106
  end
@@ -5,7 +5,18 @@ module DynamicScaffold
5
5
  # Get the hash of the key and value specified for the scope.
6
6
  def scope_params
7
7
  return {} if dynamic_scaffold.scope.nil?
8
- dynamic_scaffold.scope.each_with_object({}) {|attr, res| res[attr] = params[attr] }
8
+ case dynamic_scaffold.scope
9
+ when Array then
10
+ dynamic_scaffold.scope.each_with_object({}) do |val, res|
11
+ if val.is_a? Hash
12
+ val.each {|k, v| res[k] = v }
13
+ else
14
+ res[val] = params[val]
15
+ end
16
+ end
17
+ when Hash then
18
+ dynamic_scaffold.scope
19
+ end
9
20
  end
10
21
 
11
22
  # Convert pkey_string value to hash.
@@ -112,7 +123,23 @@ module DynamicScaffold
112
123
  end
113
124
 
114
125
  def request_queries(*except)
115
- request.query_parameters.to_hash.delete_if{|k, v| except.select(&:present?).include?(k.to_sym)}
126
+ request.query_parameters.to_hash.delete_if {|k, _v| except.select(&:present?).include?(k.to_sym) }
127
+ end
128
+
129
+ def check_max_count!
130
+ return if dynamic_scaffold.max_count.nil?
131
+ instance_exec(@record, &dynamic_scaffold.lock_before_count) if dynamic_scaffold.lock_before_count
132
+ count_query = dynamic_scaffold.list.build_sql(scope_params)
133
+ count_query = count_query.lock if dynamic_scaffold.max_count_options[:lock]
134
+ count = count_query.count
135
+ raise Error::InvalidOperation, 'You can not add any more.' if dynamic_scaffold.max_count?(count)
136
+ end
137
+
138
+ def handle_pagination(query)
139
+ return query unless dynamic_scaffold.list.pagination
140
+ query
141
+ .page(params[dynamic_scaffold.list.pagination.param_name])
142
+ .per(dynamic_scaffold.list.pagination.per_page)
116
143
  end
117
144
  end
118
145
  end
@@ -13,10 +13,7 @@ module DynamicScaffold
13
13
  @classnames_list.push(classnames) if classnames
14
14
  @notes = []
15
15
  @multiple = type == :collection_check_boxes || html_attributes[:multiple]
16
- @inserts = {
17
- before: [],
18
- after: []
19
- }
16
+ @inserts = { before: [], after: [] }
20
17
  end
21
18
 
22
19
  def notes?
@@ -106,7 +103,7 @@ module DynamicScaffold
106
103
 
107
104
  def errors(record)
108
105
  msg = record.errors.full_messages_for(proxy_field.name)
109
- rel = @config.model.reflect_on_all_associations.find{|r| r.foreign_key.to_s == name.to_s}
106
+ rel = @config.model.reflect_on_all_associations.find {|r| r.foreign_key.to_s == name.to_s }
110
107
  msg.concat(record.errors.full_messages_for(rel.name)) if rel.present?
111
108
  msg
112
109
  end
@@ -4,7 +4,7 @@ module DynamicScaffold
4
4
  Rails.cache.fetch "dynamic_scaffold/fontawesome/icons/#{path}" do
5
5
  full_path = DynamicScaffold::Engine.root.join('app', 'assets', 'images', 'dynamic_scaffold', 'fontawesome', path)
6
6
  file = File.open(full_path)
7
- file.read.gsub!('<svg ', '<svg class="dynamicScaffold-svg-icon" ').html_safe # rubocop:disable Rails/OutputSafety, Metrics/LineLength
7
+ file.read.gsub!('<svg ', '<svg class="ds-svg-icon" ').html_safe # rubocop:disable Rails/OutputSafety
8
8
  end
9
9
  end
10
10
  end
@@ -1,3 +1,3 @@
1
1
  module DynamicScaffold
2
- VERSION = '0.6.1'.freeze
2
+ VERSION = '0.7.0'.freeze
3
3
  end
@@ -14,7 +14,7 @@ module DynamicScaffold
14
14
  end
15
15
 
16
16
  module List
17
- autoload :Item, 'dynamic_scaffold/list/item'
17
+ autoload :Item, 'dynamic_scaffold/list/item'
18
18
  end
19
19
 
20
20
  module Form
@@ -16,6 +16,14 @@ class <%= @class_scope.present? ? "#{@class_scope}::" : '' %><%= @plural_model_n
16
16
  # Please see https://github.com/gomo/dynamic_scaffold#view-helper for details.
17
17
  # config.title.name = 'Model'
18
18
 
19
+ # To enable scoping, call scope with parameter names you want.
20
+ # Please see https://github.com/gomo/dynamic_scaffold#scoping for details.
21
+ # config.scope([role])
22
+
23
+ # You can specify the maximum count of registrations.
24
+ # Please see https://github.com/gomo/dynamic_scaffold#limit-count for details.
25
+ # config.max_count 10
26
+
19
27
  # When you want a simple sort on the column of record, please call order.
20
28
  # config.list.order created_at: :desc
21
29
 
@@ -37,14 +45,12 @@ class <%= @class_scope.present? ? "#{@class_scope}::" : '' %><%= @plural_model_n
37
45
  # highlight_current: false, # Whether to highlight the current page.
38
46
  # )
39
47
 
40
- # To enable scoping, call scope with parameter names you want.
41
- # Please see https://github.com/gomo/dynamic_scaffold#scoping for details.
42
- # config.scope([role])
43
-
44
- # You can change the items displayed in the list through the `config.list.item`.
45
- # Please see https://github.com/gomo/dynamic_scaffold#customize-list for details.
48
+ # If you want filtering that can not be handled by `config.scope`, you can use the filter method.
49
+ # config.list.filter do |query|
50
+ # query.where(parent_id: nil)
51
+ # end
46
52
 
47
- # You can set each title in the list through title method.
53
+ # You can set each title in the list header through title method.
48
54
  # Pass the attribute name,
49
55
  # config.list.title(:name)
50
56
  # or
@@ -52,6 +58,9 @@ class <%= @class_scope.present? ? "#{@class_scope}::" : '' %><%= @plural_model_n
52
58
  # record.name
53
59
  # end
54
60
 
61
+ # You can change the items displayed in the list through the `config.list.item`.
62
+ # Please see https://github.com/gomo/dynamic_scaffold#customize-list for details.
63
+
55
64
  <%- @model.column_names.each do |column| -%>
56
65
  config.list.item(:<%= column %>, style: 'width: 120px;')
57
66
  <%- end -%>
@@ -65,8 +74,9 @@ class <%= @class_scope.present? ? "#{@class_scope}::" : '' %><%= @plural_model_n
65
74
 
66
75
  # def index
67
76
  # super do |query|
68
- # # If you want append database queries, Write here.
69
- # # Do not forget to return query.
77
+ # # If you want to add a search to the page, please add it to the query here.
78
+ # # The condition added here does not affect max_count.
79
+ # # Please note that returning nil will be ignored.
70
80
  # end
71
81
  # end
72
82
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamic_scaffold
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Masamoto Miyata
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-04 00:00:00.000000000 Z
11
+ date: 2018-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: classnames-rails-view
@@ -295,7 +295,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
295
295
  version: '0'
296
296
  requirements: []
297
297
  rubyforge_project:
298
- rubygems_version: 2.7.6
298
+ rubygems_version: 2.7.7
299
299
  signing_key:
300
300
  specification_version: 4
301
301
  summary: The Scaffold system which dynamically generates CRUD and sort functions.