DynamicScaffold
The Scaffold system which dynamically generates CRUD and sort functions.
Feature
- This is generate the pages using same views dynamically.
- Support the responsive design and touch UI.
- Support sort and pagination.
- Support image upload with preview.
- Support image crop.
- Support globalize form fields.
- Support cocoon.
- This has the views with the Twitter Bootstrap. Support bootstrap3/4.
- Customizable and flexible.
Installation
Add this line to your application's Gemfile:
gem 'dynamic_scaffold', '~> 0.1'
And then execute:
$ bundle
Usage
Routes
Please call dynamic_scaffold_for
method with the resource name.
Rails.application.routes.draw do
dynamic_scaffold_for 'shops'
end
This will generate the following routes.
sort_or_destroy_controls_master_shops PATCH /:locale/controls/master/shops/sort_or_destroy(.:format) controls/shops#sort_or_destroy
controls_master_shops GET /:locale/controls/master/shops(.:format) controls/shops#index
POST /:locale/controls/master/shops(.:format) controls/shops#create
new_controls_master_shop GET /:locale/controls/master/shops/new(.:format) controls/shops#new
edit_controls_master_shop GET /:locale/controls/master/shops/:id/edit(.:format) controls/shops#edit
controls_master_shop PATCH /:locale/controls/master/shops/:id(.:format) controls/shops#update
PUT /:locale/controls/master/shops/:id(.:format) controls/shops#update
Generate controller and views
First, you need a model of the target table. If you have not generate it yet, please generate the model.
rails generate model Shop
Next, execute the following command for generate the controller and views.
rails generate dynamic_scaffold shops
create app/controllers/shops_controller.rb
create app/views/shops/edit.html.erb
create app/views/shops/index.html.erb
create app/views/shops/new.html.erb
You can also specify namespaces and the model name if you want.
rails generate dynamic_scaffold namespace/plural_model
rails generate dynamic_scaffold namespace/controller Model
Options
content_for
Enclose the render
of view in content_for
.
rails generate dynamic_scaffold admin/shops --content_for admin_body
# views/admin/shops/edit.html.erb
<% content_for :admin_body do%>
<%= render 'dynamic_scaffold/bootstrap/edit' %>
<%end%>
controller_base
Change the base class of controller.
rails generate dynamic_scaffold admin/shops --controller_base Admin::BaseController
# controllers/admin/shops_controller.rb
class Admin::ShopsController < Admin::BaseController
include DynamicScaffold::Controller
dynamic_scaffold Shop do |config|
Prepare CSS and Javascript
You need to load the files for CSS and Javascript. Currently, we support the Bootstrap 3 and Bootstrap 4.
# app/assets/stylesheets/application.scss
@import 'dynamic_scaffold/bootstrap3'
# or
@import 'dynamic_scaffold/bootstrap4'
Customization
You can customize each items in the block passed as dynamic_scaffold method argument.
class ShopController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold Shop do |config|
end
end
Customize list
You can customize the list through the DynamicScaffold::Config#list
property.
class ShopController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold Shop do |config|
config.list.add_button = false
config.list.add_button do
false if params[:foo] == 1
end
config.list.edit_buttons = false
config.list.edit_buttons do |record|
false if record.id != 1
end
config.list.destroy_buttons = false
config.list.destroy_buttons do |record|
false if record.id != 1
end
config.list.filter do |query|
query.where(parent_id: nil)
end
config.list.title(:name)
config.list.row_class do |record|
'disabled' unless record.active?
end
config.list.item(:id, style: 'width: 80px').label('Number')
config.list.item :name, style: 'width: 120px'
config.list.item :updated_at, style: 'width: 180px' do |rec, name|
rec.fdate name, '%Y-%m-%d %H:%M:%S'
end
config.list.item do |rec|
link_to "Show #{rec.name}", controls_master_shops_path
end
config.list.item(:name).show_only {|rec| rec.foobar? }
end
end
Customize form
You can customize the form through the DynamicScaffold::Config#form
property.
class ShopController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold Shop do |config|
config.form.item :text_field, :name
config.form.item(:text_field, :name).label('Shop Name', class: 'h3')
config.form.item(:text_field, :name).label do |text, depth, attrs|
tag.div class: 'h3 mb-1' do
tag.label text
end
end
config.form.item(:text_field, :name).default('Foo Bar')
config.form.item(:text_field, :name).default do
Shop.find_by(shop_id: params[:shop_id])
end
config.form.item :hidden_field, :id
config.form.item(:hidden_field, :id).label 'ID'
config.form.item :text_area, :memo, rows: 8
config.form.item(:collection_select,
:category_id, Category.all, :id, :name, include_blank: 'Select Category'
)
config.form.item(:collection_check_boxes, :state_ids, State.all, :id, :name)
config.form.item(:collection_radio_buttons, :status, Shop.statuses.map{|k, _v| [k, k.titleize]}, :first, :last)
config.form.item(:collection_select, :status, -> { Shop.where(area_id: params[:area_id]) }, :first, :last)
config.form.item :block, :free do |form, field|
content_tag :div, class: 'foobar' do
form.text_field field.name, class: 'foobar'
end
end
config.form.item(:block, :free).label 'Free Value' do |form, field|
content_tag :div, class: 'foobar' do
form.text_field field.name, class: 'foobar'
end
end
config.form.item(:file_field, :image).label('Image').insert(:after) do |rec|
tag.label for: :delete_image do
concat tag.input type: :checkbox, id: :delete_image, name: 'shop[delete_image]'
concat 'Delete image'
end
end
config.form.permit_params(:delete_image)
config.form.item(:text_field, :name).note do
concat(tag.p do
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
end)
concat(tag.p do
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
end)
end
end
end
carrierwave_image
You can add an image uploader with preview to the form. It uses carrierwave to save images and associate with records.
For example, you mount the carrierwave uploader on the thumb
column of Shop model.
class Shop < ApplicationRecord
mount_uploader :thumb, ShopThumbUploader
The controller code is as follows.
class ShopController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold Shop do |config|
...
config.form.item(
:carrierwave_image,
:thumb,
preview_max_size: {width: '300px', height: '300px'},
removable: false
)
The carrierwave_image
supports cropper too. For details, please read Crop the image.
globalize_fields
We support globalize. Below is the controller code.
class ShopController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold Shop do |config|
...
c.form
.item(:globalize_fields, { en: 'English', ja: 'Japanese' }, style: 'width: 78px;')
.for(:text_field, :keyword)
Setting the model for globalize_fields with validates is here.
cocoon
We support cocoon.
c.form.item(:cocoon, :shop_memos, add_text: 'Add Memo') do |form|
form.item(:hidden_field, :id)
form.item(:text_field, :title)
form.item(:text_area, :body)
end.filter do |records|
records.partition do |rec|
rec.id.nil?
end.yield_self do |nils, others|
nils + others.sort_by {|rec| -rec.id }
end
end
json_object
You can save json object string in a column using each form items and validations.
Check this wiki
Overwrite actions
You can pass the block to super in index/create/update actions.
def index
super do |records|
end
end
def update|create
super do |record|
# `record` is a Model.
# This block is called before saving.
end
end
Sorting
You can sort records having integer column for order in the list page.
class CreateCountries < ActiveRecord::Migration[5.1]
def change
create_table :countries do |t|
t.string :name
t.integer :sequence
end
end
end
rails generate dynamic_scaffold countries
class CountriesController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold Country do |config|
config.list.sorter sequence: :desc
...
You can enable pagination with kaminari.
class ShopController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold Shop do |config|
config.list.pagination per_page: 20
...
The following options are available for the pagination.
window: 0, # kaminari options
outer_window: 0, # kaminari options
left: 0, # kaminari options
right: 0, # kaminari options
param_name: :page, # kaminari options
total_count: true, # Whether to display total count and active page, like `2/102`.
end_buttons: true, # Whether to display buttons to the first and last page.
neighbor_buttons: true, # Whether to display buttons to the next and prev page.
gap_buttons: false, # Whether to display gap buttons.
highlight_current: false, # Whether to highlight the current page.
Scoping
You can scoping for target items by url param.
For example, you create the Scaffold of users for each role.
create_table :users do |t|
t.string :email, null: false
t.string :encrypted_password, null: false
t.integer :role, limit: 2, null: false
end
class User < ApplicationRecord
enum role: { admin: 1, staff: 2, member: 3 }
end
Set the route as follows.
Rails.application.routes.draw do
dynamic_scaffold_for 'users/:role', controller: 'users', as: 'users', role: Regexp.new(User.roles.keys.join('|'))
end
sort_or_destroy_controls_master_users PATCH /:locale/controls/master/users/:role/sort_or_destroy(.:format) controls/users#sort_or_destroy {:role=>/admin|staff|member/}
controls_master_users GET /:locale/controls/master/users/:role(.:format) controls/users#index {:role=>/admin|staff|member/}
POST /:locale/controls/master/users/:role(.:format) controls/users#create {:role=>/admin|staff|member/}
new_controls_master_user GET /:locale/controls/master/users/:role/new(.:format) controls/users#new {:role=>/admin|staff|member/}
edit_controls_master_user GET /:locale/controls/master/users/:role/:id/edit(.:format) controls/users#edit {:role=>/admin|staff|member/}
controls_master_user PATCH /:locale/controls/master/users/:role/:id(.:format) controls/users#update {:role=>/admin|staff|member/}
PUT /:locale/controls/master/users/:role/:id(.:format) controls/users#update {:role=>/admin|staff|member/}
For the controller, as follows.
class UsersController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold User do |c|
c.scope [:role]
...
The scope can fix value also.
class UsersController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold User do |c|
c.scope [{role: :admin}]
c.scope role: :admin
...
By default, the value can not update to a value other than the value specified in the scope( or parameter). This behavior can change with changeable
option.
class UsersController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold User do |c|
c.scope([:role], changeable: true)
...
Limit count
You can specify the maximum count of registrations.
class UsersController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold User do |c|
c.max_count 10
...
If database support lock, you can lock the table before count.
class UsersController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold User do |c|
c.max_count 10, lock: true
...
If you want a finer lock control, you can use the block.
class UsersController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold User do |c|
c.max_count 10 do |record|
ActiveRecord::Base.connection.execute("...")
end
...
Please note that the count of records is affected by scope and list.filter.
View helper
dynamic_scaffold.title
You can set and get the title of the pages.
# app/views/your_resources/list.html.erb
<%= dynamic_scaffold.title.current.name %>
<!-- Shop -->
<%= dynamic_scaffold.title.current.action %>
<!-- List -->
<%= dynamic_scaffold.title.current.full %>
<!-- Shop List -->
You can get another action title through the action name method too.
# app/views/your_resources/list.html.erb
<%= dynamic_scaffold.title.new.full %>
<!-- Create Shop -->
If you want change from the model name, set config.title.name
.
class ShopController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold Shop do |config|
config.title.name = 'Model'
...
If you want to dynamically set according to url parameters, you can also use block.
class ShopController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold Shop do |config|
config.title.name do
I18n.t "enum.user.role.#{params[role]}", default: params[role].titleize
end
dynamic_scaffold_path
You can get the path by specifying the action name. Below is an example of displaying breadcrumbs.
<ol class="breadcrumb">
<%= yield :breadcrumb_before %>
<% if params['action'] == 'index' %>
<li class="active"><%= dynamic_scaffold.title.current.name %></li>
<% else %>
<li><%= link_to dynamic_scaffold.title.index.name, dynamic_scaffold_path(:index) %></li>
<li class="active"><%= dynamic_scaffold.title.current.action %></li>
<% end %>
</ol>
dynamic_scaffold.vars
You can cache, such as data to be displayed multiple times in a view using dynamic_scaffold.vars
.
class ShopController < ApplicationController
include DynamicScaffold::Controller
dynamic_scaffold Shop do |config|
config.title.vars :shop_type do
ShopType.find(params['shop_type_id'])
end
...
<%= dynamic_scaffold.vars.shop_type.name %>
Contributing
- We use rspec for test.
- Check code with rubocop.
Development hints.
Please support both Bootstrap 3/4. You can change CSS to Bootstrap3 by registering bootstrap=3
in cookie in the sample page.
License
The gem is available as open source under the terms of the MIT License.