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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +465 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/dynamic_scaffold_manifest.js +0 -0
  6. data/app/assets/images/dynamic_scaffold/fontawesome/README.md +9 -0
  7. data/app/assets/images/dynamic_scaffold/fontawesome/angle-double-down.svg +1 -0
  8. data/app/assets/images/dynamic_scaffold/fontawesome/angle-double-up.svg +1 -0
  9. data/app/assets/images/dynamic_scaffold/fontawesome/chevron-down.svg +1 -0
  10. data/app/assets/images/dynamic_scaffold/fontawesome/chevron-left.svg +1 -0
  11. data/app/assets/images/dynamic_scaffold/fontawesome/chevron-right.svg +1 -0
  12. data/app/assets/images/dynamic_scaffold/fontawesome/chevron-up.svg +1 -0
  13. data/app/assets/images/dynamic_scaffold/fontawesome/exclamation-circle.svg +1 -0
  14. data/app/assets/images/dynamic_scaffold/fontawesome/hdd.svg +1 -0
  15. data/app/assets/images/dynamic_scaffold/fontawesome/pencil-alt.svg +1 -0
  16. data/app/assets/images/dynamic_scaffold/fontawesome/plus.svg +1 -0
  17. data/app/assets/images/dynamic_scaffold/fontawesome/step-backward.svg +1 -0
  18. data/app/assets/images/dynamic_scaffold/fontawesome/step-forward.svg +1 -0
  19. data/app/assets/images/dynamic_scaffold/fontawesome/times.svg +1 -0
  20. data/app/assets/javascripts/dynamic_scaffold/common.js +95 -0
  21. data/app/assets/javascripts/dynamic_scaffold/delete.js +57 -0
  22. data/app/assets/javascripts/dynamic_scaffold/pagination.js +26 -0
  23. data/app/assets/javascripts/dynamic_scaffold/sorter.js +120 -0
  24. data/app/assets/javascripts/dynamic_scaffold.js +4 -0
  25. data/app/assets/stylesheets/dynamic_scaffold/bootstrap3.scss +22 -0
  26. data/app/assets/stylesheets/dynamic_scaffold/bootstrap4.scss +34 -0
  27. data/app/assets/stylesheets/dynamic_scaffold/common.scss +130 -0
  28. data/app/assets/stylesheets/dynamic_scaffold/resplist.scss +155 -0
  29. data/app/views/dynamic_scaffold/bootstrap/_edit.html.erb +1 -0
  30. data/app/views/dynamic_scaffold/bootstrap/_form.html.erb +53 -0
  31. data/app/views/dynamic_scaffold/bootstrap/_list.html.erb +88 -0
  32. data/app/views/dynamic_scaffold/bootstrap/_new.html.erb +1 -0
  33. data/app/views/dynamic_scaffold/bootstrap/_pagination.html.erb +5 -0
  34. data/app/views/dynamic_scaffold/bootstrap/_save_order.html.erb +7 -0
  35. data/app/views/dynamic_scaffold/bootstrap/kaminari/_first_page.html.erb +7 -0
  36. data/app/views/dynamic_scaffold/bootstrap/kaminari/_gap.html.erb +3 -0
  37. data/app/views/dynamic_scaffold/bootstrap/kaminari/_last_page.html.erb +7 -0
  38. data/app/views/dynamic_scaffold/bootstrap/kaminari/_next_page.html.erb +7 -0
  39. data/app/views/dynamic_scaffold/bootstrap/kaminari/_page.html.erb +9 -0
  40. data/app/views/dynamic_scaffold/bootstrap/kaminari/_paginator.html.erb +17 -0
  41. data/app/views/dynamic_scaffold/bootstrap/kaminari/_prev_page.html.erb +7 -0
  42. data/config/locales/en.yml +19 -0
  43. data/config/locales/ja.yml +28 -0
  44. data/config/routes.rb +2 -0
  45. data/lib/dynamic_scaffold/config.rb +263 -0
  46. data/lib/dynamic_scaffold/controller.rb +116 -0
  47. data/lib/dynamic_scaffold/controller_utilities.rb +115 -0
  48. data/lib/dynamic_scaffold/engine.rb +18 -0
  49. data/lib/dynamic_scaffold/error/base.rb +6 -0
  50. data/lib/dynamic_scaffold/error/controller.rb +6 -0
  51. data/lib/dynamic_scaffold/error/invalid_icon.rb +6 -0
  52. data/lib/dynamic_scaffold/form/item/base.rb +106 -0
  53. data/lib/dynamic_scaffold/form/item/block.rb +16 -0
  54. data/lib/dynamic_scaffold/form/item/single_option.rb +21 -0
  55. data/lib/dynamic_scaffold/form/item/two_options.rb +28 -0
  56. data/lib/dynamic_scaffold/form/item/two_options_with_block.rb +19 -0
  57. data/lib/dynamic_scaffold/icons/fontawesome.rb +47 -0
  58. data/lib/dynamic_scaffold/list/item.rb +35 -0
  59. data/lib/dynamic_scaffold/routes.rb +23 -0
  60. data/lib/dynamic_scaffold/version.rb +3 -0
  61. data/lib/dynamic_scaffold.rb +12 -0
  62. data/lib/generators/dynamic_scaffold/USAGE +19 -0
  63. data/lib/generators/dynamic_scaffold/dynamic_scaffold_generator.rb +25 -0
  64. data/lib/generators/dynamic_scaffold/templates/controller.erb +100 -0
  65. data/lib/generators/dynamic_scaffold/templates/views/edit.erb +1 -0
  66. data/lib/generators/dynamic_scaffold/templates/views/index.erb +1 -0
  67. data/lib/generators/dynamic_scaffold/templates/views/new.erb +1 -0
  68. data/lib/tasks/dynamic_scaffold_tasks.rake +4 -0
  69. 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,5 @@
1
+ <%if dynamic_scaffold.list.pagination %>
2
+ <div class="dynamicScaffold-row">
3
+ <%= paginate @records, {views_prefix: 'dynamic_scaffold/bootstrap'}.merge(dynamic_scaffold.list.pagination.kaminari_options) %>
4
+ </div>
5
+ <%end%>
@@ -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,3 @@
1
+ <li class='page-item disabled gap dynamicScaffoldJs-page-item'>
2
+ <%= link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link' %>
3
+ </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,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -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