在讲解基本的表单架构以前,我们先将基本的CRUD
建立起来。
以下的前情提要会提到有关mvc
& routes
的建设,还不熟悉 Rails 框架的读者们,可以趁者以下举的例子熟悉基本的架构
首先我们先建立部落格的model
。部落格的栏位 ➡️ 标题、内文、种类、创建者
rails g model Blog title content user:belongs_to genre:integer
确认无误migration
以後,我们进行资料库migrate
class CreateBlogs < ActiveRecord::Migration[6.0]
def change
create_table :blogs do |t|
t.string :title
t.string :content
t.references :user, null: false
t.integer :genre
t.timestamps
end
end
end
rails db:migrate
资料迁徙完後,开始做以下设定
user
做 foreign key
做设定。
has_many
blogsbelongs_to
userinteger
,并使用Rails
内建的enum
Blog
的3个栏位:title
, :content
:genre
不能为空值# 备注: 由於这边不会带到会员,所以假设会员都已经设定完成的前提下进行串接
class User < ApplicationRecord
has_many :blogs
end
class Blog < ApplicationRecord
belongs_to :user
validates :title, :content, :genre, presence: true
enum genre: {
life: 0,
casual: 1,
technology: 2,
}
end
建立完资料库以後,就可以实作资料的新增、删除、改动的动作。因此创建完以後我会先在Rails Console
动手实作一遍
rails console
#===== 确定存在的栏位为空值的时候不能存取
User.first.blogs.create!
# ActiveRecord::RecordInvalid (校验失败: Title 不能为空白, Content 不能为空白, Genre 不能为空白)
顺道一提,如果没有加惊叹号(意思为 bang
)!
, 便不会引发错误,但这边我们可以看到id: nil
,代表实际上这笔资料没有被存进去。
User.first.blogs.create
#=> #<Blog id: nil, title: nil, content: nil, user_id: 1, genre: nil, created_at: nil, updated_at: nil>
我们也可以使用model的方法判断这笔资料在哪一个生命周期。这里我们要使用 new_record?
, persisted?
判断这一笔是否已经被存进资料库。
User.first.blogs.create.new_record? #=> true
User.first.blogs.create.persisted? #=> false
以下为 ActiveRecord 的生命周期
new record ➡️ 尚未写入阶段会判断为true
blog = Item.new
blog.new_record? #=> true
persisted ➡️ 已写入阶段会判断为true
blog.save
blog.persisted? #=> true
changed ➡️ 资料被改写但尚未存进资料库会被判断true
blog.name = "other"
blog.changed? #=> true
destroyed ➡️ 资料被删除但该笔纪录,但还没重整。因此该笔资料暂时存在在model。遇到这种情况则会判断为true
blog.destroy
blog.destroyed? #=> true
接着我们试着存入一笔
User.first.blogs.create!(title: 'Title 1', content: 'Content 1', genre: :life)
#=> #<Blog id: 1, title: "Title 1", content: "Content 1", user_id: 1, genre: "life", created_at: "2021-09-18 12:11:56", updated_at: "2021-09-18 12:11:56">
User Load (1.0ms) SELECT `users`.* FROM `users` WHERE `users`.`banned` = FALSE ORDER BY `users`.`id` ASC LIMIT 1
Blog Create (15.3ms) INSERT INTO `blogs` (`title`, `content`, `user_id`, `genre`, `created_at`, `updated_at`) VALUES ('Title 1', 'Content 1', 1, 0, '2021-09-18 12:11:56.986857', '2021-09-18 12:11:56.986857')
确认 model
运作可以的话,我们便可以继续下一步的操作。我们会在Day29-30讲更多关於Model
,Model
的部分就先打住
接着,我们先制造可以读取到的路径
Rails.application.routes.draw do
namespace :admin do
# 基本路径
resources :blogs do
post :spectial_update, on: :collection
end
end
end
使用了resources
,Rails会创造8种路径。
$ rails routes -g blog
Prefix Verb URI Pattern Controller#Action
spectial_update_admin_blogs POST /admin/blogs/spectial_update(.:format) admin/blogs#spectial_update
admin_blogs GET /admin/blogs(.:format) admin/blogs#index
POST /admin/blogs(.:format) admin/blogs#create
new_admin_blog GET /admin/blogs/new(.:format) admin/blogs#new
edit_admin_blog GET /admin/blogs/:id/edit(.:format) admin/blogs#edit
admin_blog GET /admin/blogs/:id(.:format) admin/blogs#show
PATCH /admin/blogs/:id(.:format) admin/blogs#update
PUT /admin/blogs/:id(.:format) admin/blogs#update
DELETE /admin/blogs/:id(.:format) admin/blogs#destroy
接着我们将controller
所有的动作内容定义出来(以下内容会慢慢带到)
# in app/controllers/admin/blogs_controller.rb
#
module Admin
class BlogsController < ApplicationController
before_action :set_blog, only: %i(show edit update)
SPECIAL_ARTICLE = 1..3
def index
# 单笔资料新增一笔待储存的纪录
@blog = current_user.blogs.new
end
# 创建一笔新的纪录
def create
end
# 创建或修改前10笔
def special_update
end
def show; end
end
end
我们先建立部落格相关的列表页 index
module BlogHelper
def blog_genre
[
{ id: 'index', wording: '列表页' },
{ id: 'special', wording: '特殊新增' },
]
end
end
我们使用 Day22 介绍的自定义 helper 组成我们要的画面
// in app/views/admin/blogs/index.html.slim
= title '部落格列表'
/ 卡片内容
= card do
= card_header(title: '部落格列表') do
= link_to '新增', '#', class: 'ml-auto btn btn-primary',
data: { toggle: 'modal', target: '#new-blog-modal' }
= card_body do
= tab_list(blog_genre)
= tab_contents do
/ 当前显示内容
= tab_active_content(blog_genre.first[:id]) do
/ 页签1内容
= tag.span "列表页"
/ 隐藏内容
= tab_content(blog_genre.second[:id]) do
/ 页签2内容
= tag.span "特殊新增"
/ 弹跳视窗
= modal(id: 'new-blog-modal', confirm_wording: '送出文章',
confirm_form: nil, title: '新增文章') do
= tag.span "我是弹跳视窗内容"
画面上有几个看点
新增按钮使用的是ml-auto
,若没有margin-left: auto
则会是以下的样子。在 Day19, Day20 提过margin
相关技巧。
页签的切换与弹跳视窗的自定义helper,在 Day22 介绍过。
将基本的环境设置完後,我们便开始使用simple_form
写新增弹跳视窗的表单
/ 弹跳视窗
= modal(id: 'new-blog-modal', confirm_wording: '送出文章',
confirm_form: 'new_modal', title: '新增文章') do
= simple_form_for [:admin, @blog], html: { method: :post, id: :new_modal } do |f|
= f.input :title, label: tag.strong('标题')
= f.input :content, label: tag.strong('内文')
= f.input :genre, as: :select, label: tag.strong('分类')
而产生的画面如下 ⬇️
我们可以看到,当我们将标题、内文、分类设为必填之後,simple_form
帮忙新增了米字号*
。
而不幸的是,simple_form
没有办法帮我们判断是否有使用enum
因此我们要将选项填上去,并且使用:selected
预设选择休闲
/ 弹跳视窗
= modal(id: 'new-blog-modal', confirm_wording: '送出文章',
confirm_form: 'new_modal', title: '新增文章') do
= simple_form_for [:admin, @blog], html: { method: :post, id: :new_modal } do |f|
= f.input :title, label: tag.strong('标题')
= f.input :content, label: tag.strong('内文')
= f.input :genre, as: :select, label: tag.strong('分类'),
collection: [["生活", :life], ["休闲", :casual], ["科技", :technology]],
selected: :casual
接着我们看看简单的使用simple_form
查看对应的html
画面为何
<form
class="simple_form new_blog"
id="new_modal"
novalidate="novalidate"
action="/admin/blogs"
accept-charset="UTF-8"
method="post"
>
<input
type="hidden"
name="authenticity_token"
value="jJ2jdgkIRXRXE8Ioz7i3kEe3Se/tVTXMJd0qAsB+Sw2DJvCz2Y2i9P2H50w2lfPS6uDY9F5x235kXIxUqHdkZA=="
/>
<div class="form-group string required blog_title">
<label class="string required" for="blog_title"
><strong>标题</strong> <abbr title="required">*</abbr></label
><input
class="form-control string required"
type="text"
name="blog[title]"
id="blog_title"
/>
</div>
<div class="form-group string required blog_content">
<label class="string required" for="blog_content"
><strong>内文</strong> <abbr title="required">*</abbr></label
><input
class="form-control string required"
type="text"
name="blog[content]"
id="blog_content"
/>
</div>
<div class="form-group select required blog_genre">
<label class="select required" for="blog_genre"
><strong>分类</strong> <abbr title="required">*</abbr></label
><select
class="form-control select required"
name="blog[genre]"
id="blog_genre"
>
<option value="life">生活</option>
<option selected="selected" value="casual">休闲</option>
<option value="technology">科技</option>
</select>
</div>
</form>
除了以下这段和CSRF token
有关,其他都为与我们有关的内容。
<input
type="hidden"
name="authenticity_token"
value="..."
/>
可以看到Rails
帮忙处理很多表单的问题,让我们不必自己重刻html
。
若使用:radio_buttons
= modal(id: 'new-blog-modal', confirm_wording: '送出文章',
confirm_form: 'new_modal', title: '新增文章') do
= simple_form_for [:admin, @blog], html: { method: :post, id: :new_modal } do |f|
= f.input :title, label: tag.strong('标题')
= f.input :content, label: tag.strong('内文')
= f.input :genre, as: :radio_buttons, label: tag.strong('分类'),
collection: [["生活", :life], ["休闲", :casual], ["科技", :technology]],
selected: :casual
当我们加入了item_wrapper_class: 'form-check form-check-inline'
排列成inline
,会是我们预期的样子。
= simple_form_for [:admin, @blog], html: { method: :post, id: :new_modal } do |f|
= f.input :title, label: tag.strong('标题')
= f.input :content, label: tag.strong('内文')
= f.input :genre, as: :radio_buttons, label: tag.strong('分类'),
collection: [["生活", :life], ["休闲", :casual], ["科技", :technology]],
selected: :casual, item_wrapper_class: 'form-check form-check-inline'
即便方便,在simple_form
上面会遇到一些困难
网路上相关资讯不多,若经验不足的读者也许可以在自己专案内找到其他人的用法。不过我同事基本上不会用simple_form,所以我只能靠自己经验摸索
simple_form_for
若要加上html
属性,key 要为:html
里面的f.input
若要加上html
属性,key 要为:input_html
多留意radio_button
, checkbox
的用法
今天介绍了基本的CRUD
,以及基本的simple_form
技巧。明天会开始介绍表单的用法及玩法!
>>: 爬虫怎麽爬 从零开始的爬虫自学 DAY9 python字串怎麽用
前文介绍了 BrowserStack 本篇写一些在撰写测项的写法与一些要注意的小地方 首先 Brow...
问题回答 Vue 3 会为 data 建立一个 Proxy 物件,并在里面建立 getter 和 s...
API流程 I have A Nonce, I have A key, Uh It's time t...
今天讲的内容属於简单的元件使用,而我在前面几天已经先有拿来用几次来观察结果,但我一直没有好好提到,今...
单元测试基础的示范专案 HelloBank 收尾与现阶段总结 我们在Day 4-Visual Stu...