dynamic_scaffold 0.1.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +465 -0
- data/Rakefile +32 -0
- data/app/assets/config/dynamic_scaffold_manifest.js +0 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/README.md +9 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/angle-double-down.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/angle-double-up.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/chevron-down.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/chevron-left.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/chevron-right.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/chevron-up.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/exclamation-circle.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/hdd.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/pencil-alt.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/plus.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/step-backward.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/step-forward.svg +1 -0
- data/app/assets/images/dynamic_scaffold/fontawesome/times.svg +1 -0
- data/app/assets/javascripts/dynamic_scaffold/common.js +95 -0
- data/app/assets/javascripts/dynamic_scaffold/delete.js +57 -0
- data/app/assets/javascripts/dynamic_scaffold/pagination.js +26 -0
- data/app/assets/javascripts/dynamic_scaffold/sorter.js +120 -0
- data/app/assets/javascripts/dynamic_scaffold.js +4 -0
- data/app/assets/stylesheets/dynamic_scaffold/bootstrap3.scss +22 -0
- data/app/assets/stylesheets/dynamic_scaffold/bootstrap4.scss +34 -0
- data/app/assets/stylesheets/dynamic_scaffold/common.scss +130 -0
- data/app/assets/stylesheets/dynamic_scaffold/resplist.scss +155 -0
- data/app/views/dynamic_scaffold/bootstrap/_edit.html.erb +1 -0
- data/app/views/dynamic_scaffold/bootstrap/_form.html.erb +53 -0
- data/app/views/dynamic_scaffold/bootstrap/_list.html.erb +88 -0
- data/app/views/dynamic_scaffold/bootstrap/_new.html.erb +1 -0
- data/app/views/dynamic_scaffold/bootstrap/_pagination.html.erb +5 -0
- data/app/views/dynamic_scaffold/bootstrap/_save_order.html.erb +7 -0
- data/app/views/dynamic_scaffold/bootstrap/kaminari/_first_page.html.erb +7 -0
- data/app/views/dynamic_scaffold/bootstrap/kaminari/_gap.html.erb +3 -0
- data/app/views/dynamic_scaffold/bootstrap/kaminari/_last_page.html.erb +7 -0
- data/app/views/dynamic_scaffold/bootstrap/kaminari/_next_page.html.erb +7 -0
- data/app/views/dynamic_scaffold/bootstrap/kaminari/_page.html.erb +9 -0
- data/app/views/dynamic_scaffold/bootstrap/kaminari/_paginator.html.erb +17 -0
- data/app/views/dynamic_scaffold/bootstrap/kaminari/_prev_page.html.erb +7 -0
- data/config/locales/en.yml +19 -0
- data/config/locales/ja.yml +28 -0
- data/config/routes.rb +2 -0
- data/lib/dynamic_scaffold/config.rb +263 -0
- data/lib/dynamic_scaffold/controller.rb +116 -0
- data/lib/dynamic_scaffold/controller_utilities.rb +115 -0
- data/lib/dynamic_scaffold/engine.rb +18 -0
- data/lib/dynamic_scaffold/error/base.rb +6 -0
- data/lib/dynamic_scaffold/error/controller.rb +6 -0
- data/lib/dynamic_scaffold/error/invalid_icon.rb +6 -0
- data/lib/dynamic_scaffold/form/item/base.rb +106 -0
- data/lib/dynamic_scaffold/form/item/block.rb +16 -0
- data/lib/dynamic_scaffold/form/item/single_option.rb +21 -0
- data/lib/dynamic_scaffold/form/item/two_options.rb +28 -0
- data/lib/dynamic_scaffold/form/item/two_options_with_block.rb +19 -0
- data/lib/dynamic_scaffold/icons/fontawesome.rb +47 -0
- data/lib/dynamic_scaffold/list/item.rb +35 -0
- data/lib/dynamic_scaffold/routes.rb +23 -0
- data/lib/dynamic_scaffold/version.rb +3 -0
- data/lib/dynamic_scaffold.rb +12 -0
- data/lib/generators/dynamic_scaffold/USAGE +19 -0
- data/lib/generators/dynamic_scaffold/dynamic_scaffold_generator.rb +25 -0
- data/lib/generators/dynamic_scaffold/templates/controller.erb +100 -0
- data/lib/generators/dynamic_scaffold/templates/views/edit.erb +1 -0
- data/lib/generators/dynamic_scaffold/templates/views/index.erb +1 -0
- data/lib/generators/dynamic_scaffold/templates/views/new.erb +1 -0
- data/lib/tasks/dynamic_scaffold_tasks.rake +4 -0
- metadata +355 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
<%= form_with method: method, model: @record, url: url, local: true do |form| %>
|
2
|
+
<%dynamic_scaffold.form.items.each do |elem|%>
|
3
|
+
<% if elem.needs_rendering?(self)%>
|
4
|
+
<% if !elem.label? && elem.type?(:hidden_field) %>
|
5
|
+
<%= elem.render(self, form) %>
|
6
|
+
<% else %>
|
7
|
+
<div class="<%= classNames('form-group', {'has-error': @record.errors[elem.proxy_field.name].any?}) %>">
|
8
|
+
<label><%= elem.proxy_field.label %></label>
|
9
|
+
<div class="clearfix">
|
10
|
+
<% if elem.type? :collection_check_boxes %>
|
11
|
+
<%= elem.render(self, form) do |cb|%>
|
12
|
+
<div class="form-check checkbox">
|
13
|
+
<%= cb.label class: 'form-check-label' do%>
|
14
|
+
<%= cb.check_box class: 'form-check-input' %> <%= cb.text %>
|
15
|
+
<%end%>
|
16
|
+
</div>
|
17
|
+
<%end%>
|
18
|
+
<% elsif elem.type? :collection_radio_buttons %>
|
19
|
+
<%= elem.render(self, form) do |cb|%>
|
20
|
+
<div class="form-check radio">
|
21
|
+
<%= cb.label class: 'form-check-label' do%>
|
22
|
+
<%= cb.radio_button class: 'form-check-input' %> <%= cb.text %>
|
23
|
+
<%end%>
|
24
|
+
</div>
|
25
|
+
<%end%>
|
26
|
+
<% else %>
|
27
|
+
<%= elem.render(self, form, classNames('form-control', {'is-invalid': @record.errors[elem.proxy_field.name].any?})) %>
|
28
|
+
<% end %>
|
29
|
+
</div>
|
30
|
+
<%if @record.errors[elem.proxy_field.name].any?%>
|
31
|
+
<ul class="dynamicScaffold-error-message">
|
32
|
+
<%@record.errors.full_messages_for(elem.proxy_field.name).each do |err|%>
|
33
|
+
<li><%= dynamic_scaffold_icon :error %> <%= err %></li>
|
34
|
+
<%end%>
|
35
|
+
</ul>
|
36
|
+
<%end%>
|
37
|
+
<%if elem.notes?%>
|
38
|
+
<div class="form-text text-muted"><%= elem.render_notes(form.object, self) %></div>
|
39
|
+
<%end%>
|
40
|
+
</div>
|
41
|
+
<% end %>
|
42
|
+
<% end %>
|
43
|
+
<% end %>
|
44
|
+
<div>
|
45
|
+
<% path_options = dynamic_scaffold.list.pagination ? {dynamic_scaffold.list.pagination.param_name => params[:page]} : {}%>
|
46
|
+
<%= link_to dynamic_scaffold_path(:index, path_options), class: 'btn btn-outline-secondary btn-default' do%>
|
47
|
+
<%= dynamic_scaffold_icon :back %> <%= t('dynamic_scaffold.button.back') %>
|
48
|
+
<%end%>
|
49
|
+
<button type="submit" class="btn btn-outline-warning btn-warning">
|
50
|
+
<%= dynamic_scaffold_icon :save %> <%= t('dynamic_scaffold.button.save') %>
|
51
|
+
</a>
|
52
|
+
</div>
|
53
|
+
<% end %>
|
@@ -0,0 +1,88 @@
|
|
1
|
+
<%unless flash[:dynamic_scaffold_danger].nil? -%>
|
2
|
+
<div class="alert alert-danger" role="alert">
|
3
|
+
<%= flash[:dynamic_scaffold_danger] %>
|
4
|
+
</div>
|
5
|
+
<%end%>
|
6
|
+
<input type="hidden" class="authenticity_param_name" value="<%= request_forgery_protection_token %>">
|
7
|
+
<%= form_with method: :patch, url: dynamic_scaffold_path(:sort) do%>
|
8
|
+
<div class="dynamicScaffold-row">
|
9
|
+
<%= link_to dynamic_scaffold_path(:new), class: 'btn btn-outline-primary btn-primary btn-sm' do%>
|
10
|
+
<%= dynamic_scaffold_icon(:add) %> <%= t('dynamic_scaffold.button.add') %>
|
11
|
+
<%end%>
|
12
|
+
</div>
|
13
|
+
<%= render 'dynamic_scaffold/bootstrap/pagination' %>
|
14
|
+
<div class="dynamicScaffold-row">
|
15
|
+
<%= render 'dynamic_scaffold/bootstrap/save_order'%>
|
16
|
+
</div>
|
17
|
+
<div class="dynamicScaffold-row">
|
18
|
+
<% if @records.empty? %>
|
19
|
+
<p class="lead"><%= t('dynamic_scaffold.message.no_records', model: dynamic_scaffold.title.name) %></p>
|
20
|
+
<% else %>
|
21
|
+
<ul
|
22
|
+
class="resplist resplist-striped dynamicScaffoldJs-item-wrapper"
|
23
|
+
data-confirm-ok="<%= t('dynamic_scaffold.message.confirm_ok') %>"
|
24
|
+
data-confirm-ok-class="btn btn-danger btn-sm"
|
25
|
+
data-confirm-cancel="<%= t('dynamic_scaffold.message.confirm_cancel') %>"
|
26
|
+
data-confirm-cancel-class="btn btn-outline-secondary btn-default btn-sm"
|
27
|
+
>
|
28
|
+
<%@records.each do |record|%>
|
29
|
+
<li class="resplist-row dynamicScaffoldJs-item-row">
|
30
|
+
<% if dynamic_scaffold.list.title? %>
|
31
|
+
<div class="resplist-heading"><%= dynamic_scaffold.list.title(record) %></div>
|
32
|
+
<% end %>
|
33
|
+
<div class="resplist-items">
|
34
|
+
<%dynamic_scaffold.list.items.each do |disp|%>
|
35
|
+
<%= content_tag :div, class: classNames('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>
|
38
|
+
<%end%>
|
39
|
+
<%end%>
|
40
|
+
</div>
|
41
|
+
<div class="resplist-footer clearfix">
|
42
|
+
<div class="float-right pull-right">
|
43
|
+
<div class="btn-group">
|
44
|
+
<% page_param_name = dynamic_scaffold.list.pagination ? dynamic_scaffold.list.pagination.param_name : nil %>
|
45
|
+
<%= link_to dynamic_scaffold_path(:edit, id: record[record.class.primary_key], page: params[page_param_name]), class: 'btn btn-primary btn-outline-primary btn-sm' do %>
|
46
|
+
<%= dynamic_scaffold_icon(:edit) %> <%= t('dynamic_scaffold.button.edit') %>
|
47
|
+
<%end%>
|
48
|
+
</div>
|
49
|
+
<% if dynamic_scaffold.list.sorter %>
|
50
|
+
<%[*dynamic_scaffold.model.primary_key].each do |pkey|%>
|
51
|
+
<input type="hidden" name="pkeys[][<%=pkey%>]" value="<%= record[pkey] %>">
|
52
|
+
<%end%>
|
53
|
+
<div class="btn-group">
|
54
|
+
<button class="btn btn-outline-secondary btn-default btn-sm dynamicScaffoldJs-sorter-top">
|
55
|
+
<%= dynamic_scaffold_icon(:top) %>
|
56
|
+
</button>
|
57
|
+
<button class="btn btn-outline-secondary btn-default btn-sm dynamicScaffoldJs-sorter-up">
|
58
|
+
<%= dynamic_scaffold_icon(:up) %>
|
59
|
+
</button>
|
60
|
+
<button class="btn btn-outline-secondary btn-default btn-sm dynamicScaffoldJs-sorter-down">
|
61
|
+
<%= dynamic_scaffold_icon(:down) %>
|
62
|
+
</button>
|
63
|
+
<button class="btn btn-outline-secondary btn-default btn-sm dynamicScaffoldJs-sorter-bottom">
|
64
|
+
<%= dynamic_scaffold_icon(:bottom) %>
|
65
|
+
</button>
|
66
|
+
</div>
|
67
|
+
<% end %>
|
68
|
+
<div class="btn-group">
|
69
|
+
<button
|
70
|
+
data-action="<%= dynamic_scaffold_path(:update, id: record[record.class.primary_key]) %>"
|
71
|
+
data-confirm-message="<%= t('dynamic_scaffold.message.destroy_confirm') %>"
|
72
|
+
class="btn btn-danger btn-sm dynamicScaffoldJs-destory"
|
73
|
+
>
|
74
|
+
<%= dynamic_scaffold_icon(:delete) %>
|
75
|
+
</button>
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
</li>
|
80
|
+
<%end%>
|
81
|
+
</ul>
|
82
|
+
<% end %>
|
83
|
+
</div>
|
84
|
+
<div class="dynamicScaffold-row">
|
85
|
+
<%= render 'dynamic_scaffold/bootstrap/save_order'%>
|
86
|
+
</div>
|
87
|
+
<%= render 'dynamic_scaffold/bootstrap/pagination' %>
|
88
|
+
<% end %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render 'dynamic_scaffold/bootstrap/form', method: :post, url: dynamic_scaffold_path(:index) %>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<% if dynamic_scaffold.list.sorter %>
|
2
|
+
<div class="text-right">
|
3
|
+
<button type="submit" name="submit_sort" class="btn btn-outline-warning btn-warning btn-sm">
|
4
|
+
<%= dynamic_scaffold_icon :save %> <%= t('dynamic_scaffold.button.save_order') %>
|
5
|
+
</button>
|
6
|
+
</div>
|
7
|
+
<% end %>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<li class="<%= classNames 'page-item first', disabled: current_page.first? %>">
|
2
|
+
<%if current_page.first?%>
|
3
|
+
<span class="page-link"><%= dynamic_scaffold_icon(:first) %></span>
|
4
|
+
<%else%>
|
5
|
+
<%= link_to dynamic_scaffold_icon(:first), url, remote: remote, class: 'page-link' %>
|
6
|
+
<%end%>
|
7
|
+
</li>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<li class="<%= classNames 'page-item last', disabled: current_page.last? %>">
|
2
|
+
<%if current_page.last?%>
|
3
|
+
<span class="page-link"><%= dynamic_scaffold_icon(:last) %></span>
|
4
|
+
<%else%>
|
5
|
+
<%= link_to dynamic_scaffold_icon(:last), url, remote: remote, class: 'page-link' %>
|
6
|
+
<%end%>
|
7
|
+
</li>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<li class="<%= classNames 'page-item next', disabled: current_page.last? %>">
|
2
|
+
<%if current_page.last?%>
|
3
|
+
<span class="page-link"><%= dynamic_scaffold_icon(:next) %></span>
|
4
|
+
<%else%>
|
5
|
+
<%= link_to dynamic_scaffold_icon(:next), url, remote: remote, class: 'page-link' %>
|
6
|
+
<%end%>
|
7
|
+
</li>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<% if page.current? %>
|
2
|
+
<li class="<%= classNames 'page-item current dynamicScaffoldJs-page-item', {'active': dynamic_scaffold.list.pagination.highlight_current} %>">
|
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
|
+
</li>
|
5
|
+
<% else %>
|
6
|
+
<li class="<%= classNames('page-item dynamicScaffoldJs-page-item', dynamic_scaffold.list.pagination.page_class(page, @records)) %>">
|
7
|
+
<%= link_to page, url, remote: remote, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)), class: 'page-link' %>
|
8
|
+
</li>
|
9
|
+
<% end %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%= paginator.render do %>
|
2
|
+
<nav class="text-center text-xs-center">
|
3
|
+
<ul class="pagination justify-content-center dynamicScaffoldJs-pagination">
|
4
|
+
<%= first_page_tag if dynamic_scaffold.list.pagination.end_buttons %>
|
5
|
+
<%= prev_page_tag if dynamic_scaffold.list.pagination.neighbor_buttons %>
|
6
|
+
<% each_page do |page| %>
|
7
|
+
<% if page.left_outer? || page.right_outer? || page.inside_window? %>
|
8
|
+
<%= page_tag page %>
|
9
|
+
<% elsif !page.was_truncated? -%>
|
10
|
+
<%= gap_tag if dynamic_scaffold.list.pagination.gap_buttons %>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
13
|
+
<%= next_page_tag if dynamic_scaffold.list.pagination.neighbor_buttons %>
|
14
|
+
<%= last_page_tag if dynamic_scaffold.list.pagination.end_buttons %>
|
15
|
+
</ul>
|
16
|
+
</nav>
|
17
|
+
<% end %>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<li class="<%= classNames 'page-item prev', disabled: current_page.first? %>">
|
2
|
+
<%if current_page.first?%>
|
3
|
+
<span class="page-link"><%= dynamic_scaffold_icon(:prev) %></span>
|
4
|
+
<%else%>
|
5
|
+
<%= link_to dynamic_scaffold_icon(:prev), url, remote: remote, class: 'page-link' %>
|
6
|
+
<%end%>
|
7
|
+
</li>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
en:
|
2
|
+
dynamic_scaffold:
|
3
|
+
title:
|
4
|
+
full:
|
5
|
+
index: '%{name} List'
|
6
|
+
edit: 'Edit %{name}'
|
7
|
+
new: 'Create %{name}'
|
8
|
+
action:
|
9
|
+
index: 'List'
|
10
|
+
edit: 'Edit'
|
11
|
+
new: 'Create'
|
12
|
+
message:
|
13
|
+
no_records: '%{model} was not found.'
|
14
|
+
destroy_confirm: 'Are you sure you want to delete this?'
|
15
|
+
confirm_cancel: 'Cancel'
|
16
|
+
confirm_ok: 'OK'
|
17
|
+
alert:
|
18
|
+
destroy:
|
19
|
+
invalid_foreign_key: 'That item can not be deleted because it has associated record(s).'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
ja:
|
2
|
+
dynamic_scaffold:
|
3
|
+
title:
|
4
|
+
full:
|
5
|
+
index: '%{name}一覧'
|
6
|
+
edit: '%{name}編集'
|
7
|
+
new: '新規%{name}'
|
8
|
+
action:
|
9
|
+
index: '一覧'
|
10
|
+
edit: '編集'
|
11
|
+
new: '新規'
|
12
|
+
button:
|
13
|
+
add: '追加'
|
14
|
+
edit: '編集'
|
15
|
+
save_order: '並び順を保存'
|
16
|
+
save: '保存'
|
17
|
+
back: '戻る'
|
18
|
+
message:
|
19
|
+
no_records: '%{model}は見つかりませんでした。'
|
20
|
+
destroy_confirm: '削除します。よろしいですか?'
|
21
|
+
confirm_cancel: 'キャンセル'
|
22
|
+
confirm_ok: 'OK'
|
23
|
+
alert:
|
24
|
+
destroy:
|
25
|
+
invalid_foreign_key: '関連づいたレコードがあるので削除できません。'
|
26
|
+
views:
|
27
|
+
pagination:
|
28
|
+
truncate: '…'
|
data/config/routes.rb
ADDED
@@ -0,0 +1,263 @@
|
|
1
|
+
module DynamicScaffold
|
2
|
+
class Pagination
|
3
|
+
attr_reader(
|
4
|
+
:kaminari_options,
|
5
|
+
:per_page,
|
6
|
+
:total_count,
|
7
|
+
:end_buttons,
|
8
|
+
:neighbor_buttons,
|
9
|
+
:gap_buttons,
|
10
|
+
:highlight_current,
|
11
|
+
:param_name
|
12
|
+
)
|
13
|
+
def initialize(options)
|
14
|
+
options = {
|
15
|
+
per_page: 25,
|
16
|
+
window: 0, # kaminari options
|
17
|
+
outer_window: 0, # kaminari options
|
18
|
+
left: 0, # kaminari options
|
19
|
+
right: 0, # kaminari options
|
20
|
+
param_name: :page, # kaminari options
|
21
|
+
total_count: true, # Whether to display total count on active page like `2 / 102`
|
22
|
+
end_buttons: true, # Whether to display buttons to the first and last page.
|
23
|
+
neighbor_buttons: true, # Whether to display buttons to the next and prev page.
|
24
|
+
gap_buttons: false, # Whether to display gap buttons.
|
25
|
+
highlight_current: false, # Whether to highlight the current page.
|
26
|
+
}.merge(options)
|
27
|
+
@kaminari_options = options.extract!(:window, :outer_window, :left, :right, :param_name)
|
28
|
+
@param_name = @kaminari_options[:param_name]
|
29
|
+
options.each {|name, value| instance_variable_set("@#{name}", value) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def page_number(page, records)
|
33
|
+
return page unless total_count
|
34
|
+
"#{page} / #{records.total_pages}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def page_class(page, _records)
|
38
|
+
if page.inside_window?
|
39
|
+
'inner'
|
40
|
+
elsif page.left_outer?
|
41
|
+
'left-outer'
|
42
|
+
elsif page.right_outer?
|
43
|
+
'right-outer'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Title
|
49
|
+
attr_writer :name
|
50
|
+
|
51
|
+
def initialize(config)
|
52
|
+
@config = config
|
53
|
+
@titles_cache = {}
|
54
|
+
@name = @config.model.model_name.human
|
55
|
+
end
|
56
|
+
|
57
|
+
def name(&block)
|
58
|
+
if block_given?
|
59
|
+
@block = block
|
60
|
+
elsif !@block.nil?
|
61
|
+
@config.controller.view_context.instance_exec(&@block)
|
62
|
+
else
|
63
|
+
@name
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def current
|
68
|
+
public_send(@config.controller.params[:action])
|
69
|
+
end
|
70
|
+
|
71
|
+
def index
|
72
|
+
titles(:index)
|
73
|
+
end
|
74
|
+
|
75
|
+
def edit
|
76
|
+
titles(:edit)
|
77
|
+
end
|
78
|
+
|
79
|
+
def new
|
80
|
+
titles(:new)
|
81
|
+
end
|
82
|
+
|
83
|
+
# def update
|
84
|
+
# edit
|
85
|
+
# end
|
86
|
+
|
87
|
+
# def create
|
88
|
+
# create
|
89
|
+
# end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def titles(action)
|
94
|
+
unless @titles_cache[action]
|
95
|
+
titles = OpenStruct.new
|
96
|
+
titles.name = name
|
97
|
+
titles.full = I18n.t("dynamic_scaffold.title.full.#{action}", name: titles.name)
|
98
|
+
titles.action = I18n.t("dynamic_scaffold.title.action.#{action}")
|
99
|
+
titles.freeze
|
100
|
+
@titles_cache[action] = titles
|
101
|
+
end
|
102
|
+
|
103
|
+
@titles_cache[action]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Vars
|
108
|
+
def initialize(config)
|
109
|
+
@config = config
|
110
|
+
@values = {}
|
111
|
+
end
|
112
|
+
|
113
|
+
def _register(name, block)
|
114
|
+
define_singleton_method(name) do
|
115
|
+
@values[name] ||= @config.controller.instance_exec(&block)
|
116
|
+
@values[name]
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Config
|
122
|
+
attr_reader :model, :form, :list, :title, :controller
|
123
|
+
def initialize(model, controller)
|
124
|
+
@model = model
|
125
|
+
@controller = controller
|
126
|
+
@form = FormBuilder.new(self)
|
127
|
+
@list = ListBuilder.new(self)
|
128
|
+
@title = Title.new(self)
|
129
|
+
@vars = Vars.new(self)
|
130
|
+
end
|
131
|
+
|
132
|
+
def vars(name = nil, &block)
|
133
|
+
if block_given?
|
134
|
+
raise ArgumentError, 'Missing var name.' if name.nil?
|
135
|
+
@vars._register(name, block)
|
136
|
+
else
|
137
|
+
@vars
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def scope(parameter_names = nil)
|
142
|
+
@scope = parameter_names unless parameter_names.nil?
|
143
|
+
@scope
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class ListBuilder
|
148
|
+
def initialize(config)
|
149
|
+
@config = config
|
150
|
+
@items = []
|
151
|
+
@sorter = nil
|
152
|
+
@order = []
|
153
|
+
@title = nil
|
154
|
+
end
|
155
|
+
|
156
|
+
def pagination(options = nil)
|
157
|
+
@pagination = Pagination.new(options) unless options.nil?
|
158
|
+
|
159
|
+
@pagination
|
160
|
+
end
|
161
|
+
|
162
|
+
def sorter(params = nil)
|
163
|
+
@sorter = params if params
|
164
|
+
@sorter
|
165
|
+
end
|
166
|
+
|
167
|
+
def item(*args, &block)
|
168
|
+
item = List::Item.new(@config, *args, block)
|
169
|
+
@items << item
|
170
|
+
item
|
171
|
+
end
|
172
|
+
|
173
|
+
def items
|
174
|
+
if @items.empty?
|
175
|
+
@config.model.column_names.each do |column|
|
176
|
+
@items << List::Item.new(@config, column, nil)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
@items
|
180
|
+
end
|
181
|
+
|
182
|
+
def sorter_attribute
|
183
|
+
@sorter.keys.first
|
184
|
+
end
|
185
|
+
|
186
|
+
def sorter_direction
|
187
|
+
@sorter.values.first
|
188
|
+
end
|
189
|
+
|
190
|
+
def order(*args)
|
191
|
+
@order = args unless args.empty?
|
192
|
+
@order
|
193
|
+
end
|
194
|
+
|
195
|
+
def title(*args, &block)
|
196
|
+
if args[0].is_a?(Symbol) || args[0].is_a?(String) || block_given?
|
197
|
+
@title = {
|
198
|
+
column_name: args[0],
|
199
|
+
block: block
|
200
|
+
}
|
201
|
+
else
|
202
|
+
record = args[0]
|
203
|
+
return @config.controller.view_context.instance_exec(record, &@title[:block]) if @title[:block]
|
204
|
+
record.public_send(@title[:column_name])
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def title?
|
209
|
+
@title.present?
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class FormBuilder
|
214
|
+
def initialize(config)
|
215
|
+
@config = config
|
216
|
+
@items = []
|
217
|
+
end
|
218
|
+
|
219
|
+
def items
|
220
|
+
if @items.empty?
|
221
|
+
@config.model.column_names.each do |column|
|
222
|
+
type = :text_field
|
223
|
+
type = :hidden_field if @config.scope && @config.scope.include?(column.to_sym)
|
224
|
+
@items << Form::Item::SingleOption.new(@config, type, column)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
@items
|
228
|
+
end
|
229
|
+
|
230
|
+
def item(type, *args, &block) # rubocop:disable Metrics/MethodLength
|
231
|
+
case type
|
232
|
+
when
|
233
|
+
:check_box,
|
234
|
+
:radio_button,
|
235
|
+
:text_area,
|
236
|
+
:text_field,
|
237
|
+
:password_field,
|
238
|
+
:hidden_field,
|
239
|
+
:file_field,
|
240
|
+
:color_field then
|
241
|
+
item = Form::Item::SingleOption.new(@config, type, *args)
|
242
|
+
when
|
243
|
+
:time_select,
|
244
|
+
:date_select,
|
245
|
+
:datetime_select,
|
246
|
+
:collection_select,
|
247
|
+
:grouped_collection_select then
|
248
|
+
item = Form::Item::TwoOptions.new(@config, type, *args)
|
249
|
+
when
|
250
|
+
:collection_check_boxes,
|
251
|
+
:collection_radio_buttons then
|
252
|
+
item = Form::Item::TwoOptionsWithBlock.new(@config, type, *args)
|
253
|
+
when
|
254
|
+
:block then
|
255
|
+
item = Form::Item::Block.new(@config, type, *args, block)
|
256
|
+
else
|
257
|
+
raise DynamicScaffold::Error::InvalidParameter, "Unknown form item type #{type}"
|
258
|
+
end
|
259
|
+
@items << item
|
260
|
+
item
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module DynamicScaffold
|
2
|
+
module Controller
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ControllerUtilities
|
5
|
+
|
6
|
+
included do
|
7
|
+
helper_method :dynamic_scaffold_path, :dynamic_scaffold_icon, :dynamic_scaffold, :primary_key_value
|
8
|
+
attr_reader :dynamic_scaffold
|
9
|
+
before_action lambda {
|
10
|
+
@dynamic_scaffold = Config.new(self.class.dynamic_scaffold_model, self)
|
11
|
+
self.class.dynamic_scaffold_initializer.call(@dynamic_scaffold)
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
class_methods do
|
16
|
+
attr_accessor :dynamic_scaffold_model, :dynamic_scaffold_initializer
|
17
|
+
|
18
|
+
def dynamic_scaffold(model, &block)
|
19
|
+
self.dynamic_scaffold_model = model
|
20
|
+
self.dynamic_scaffold_initializer = block
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Actions
|
25
|
+
|
26
|
+
def index # rubocop:disable Metrics/AbcSize
|
27
|
+
@records = dynamic_scaffold.model.all
|
28
|
+
raise Error::Controller, '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
|
37
|
+
@records = @records.order dynamic_scaffold.list.sorter if dynamic_scaffold.list.sorter
|
38
|
+
@records = @records.order(*dynamic_scaffold.list.order) unless dynamic_scaffold.list.order.empty?
|
39
|
+
|
40
|
+
@records = yield(@records) if block_given?
|
41
|
+
raise Error::Controller, 'You must return ActiveRecord::Relation' if @records.nil?
|
42
|
+
@records
|
43
|
+
end
|
44
|
+
|
45
|
+
def new
|
46
|
+
@record = dynamic_scaffold.model.new
|
47
|
+
|
48
|
+
defaults = dynamic_scaffold.form.items.each_with_object({}) do |item, memo|
|
49
|
+
memo[item.name] = item.default if dynamic_scaffold.model.column_names.include?(item.name.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
@record.attributes = defaults.merge(scope_params)
|
53
|
+
end
|
54
|
+
|
55
|
+
def edit
|
56
|
+
@record = find_record(edit_params)
|
57
|
+
end
|
58
|
+
|
59
|
+
def create
|
60
|
+
@record = dynamic_scaffold.model.new
|
61
|
+
prev_attribute = @record.attributes
|
62
|
+
@record.attributes = update_values
|
63
|
+
bind_sorter_value(@record) if dynamic_scaffold.list.sorter
|
64
|
+
dynamic_scaffold.model.transaction do
|
65
|
+
yield(@record, prev_attribute) if block_given?
|
66
|
+
if @record.save
|
67
|
+
redirect_to dynamic_scaffold_path(:index)
|
68
|
+
else
|
69
|
+
render "#{params[:controller]}/new"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def update
|
75
|
+
values = update_values
|
76
|
+
@record = find_record(extract_pkeys(values))
|
77
|
+
prev_attribute = @record.attributes
|
78
|
+
@record.attributes = values
|
79
|
+
dynamic_scaffold.model.transaction do
|
80
|
+
yield(@record, prev_attribute) if block_given?
|
81
|
+
if @record.save
|
82
|
+
redirect_to dynamic_scaffold_path(:index)
|
83
|
+
else
|
84
|
+
render "#{params[:controller]}/edit"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def destroy
|
90
|
+
# `Destroy` also does not support multiple primary keys too.
|
91
|
+
record = find_record(dynamic_scaffold.model.primary_key => params['id'])
|
92
|
+
begin
|
93
|
+
dynamic_scaffold.model.transaction do
|
94
|
+
record.destroy
|
95
|
+
end
|
96
|
+
rescue ::ActiveRecord::InvalidForeignKey => _error
|
97
|
+
flash[:dynamic_scaffold_danger] = I18n.t('dynamic_scaffold.alert.destroy.invalid_foreign_key')
|
98
|
+
end
|
99
|
+
redirect_to dynamic_scaffold_path(:index)
|
100
|
+
end
|
101
|
+
|
102
|
+
def sort
|
103
|
+
pkeys_list = sort_params
|
104
|
+
reset_sequence(pkeys_list.size)
|
105
|
+
dynamic_scaffold.model.transaction do
|
106
|
+
pkeys_list.each do |pkeys|
|
107
|
+
rec = find_record(pkeys.to_hash)
|
108
|
+
next_sec = next_sequence!
|
109
|
+
rec[dynamic_scaffold.list.sorter_attribute] = next_sec
|
110
|
+
rec.save
|
111
|
+
end
|
112
|
+
end
|
113
|
+
redirect_to dynamic_scaffold_path(:index)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|