dynamic_scaffold 0.6.1 → 0.7.0

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