dynamic_scaffold 0.1.0

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