dynamic_scaffold 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|