Rails-Treasure chest3 嵌套表单; Ransack(3900✨)用于模糊查询, ranked-model(800🌟)自订列表顺序; PaperTrail(5000✨)跟踪model's data,auditing and versioning.
自订列表顺序, gem 'ranked-model'
多步骤表单
显示资料验证错误讯息
资料筛选和搜寻, gem 'ransack' (3900✨);
软删除和版本控制
数据汇出(csv),
自订列表顺序:ranked-model( 800✨) https://github.com/mixonic/ranked-model
gem 'ranked-model'
简单使用:
为Event增加一个column, :row_order,type是integer,加上index。
- 在model层加上include RankedModel换行ranks: row_order
- 在controller层使用: Event.rank(:row_order).all
- 更新一条记录的顺序 @event.update(:row_order_position, 0)
- 第二个参数,可以是数字,或者:first, :last, :up, :down方法。
如果使用一个普通的json controller, @event.attributes = params[:event]; @duck.save。
$.ajax({
type: 'PUT',
url: '/ducks',
dataType: 'json',
data: { duck: { row_order_position: 0 } },
});
在routes.rb中定义一个路径:member {post :reorder}
link_to "上移", reorder_admin_event_path(event, :position => :up), method: :post
link_to "下移", reorder_admin_event_path(event, :position => :down), method: :post
在路径中加上请求参数"position": "up"
复杂使用:
ranks接受几种参数:
class Duck < ActiveRecord::Base include RankedModel ranks :row_order, # 使用rank(),来定义一个ranker
:column => :sort_order # 加载这个默认列, which defaults to the name belongs_to :pond
ranks :swimming_order,
:with_same => :pond_id # Ducks belong_to Ponds, 让ranker围绕一个pond scope :walking, where(:walking => true )
ranks :walking_order,
:scope => :walking # Narrow this ranker to a scope end
当你查询时,使用rank():
Duck.rank(:row_order) Pond.first.ducks.rank(:swimming_order) Duck.walking.rank(:walking)
Ajax UI
案例(博客地址):https://www.cnblogs.com/chentianwei/p/9443664.html
多步骤表单
(Multi Step Form,又叫做 Wizards)
什么时候会用到呢? 当表单很复杂的时候,我们不希望一次就把所有字段显示出来,这样会吓跑用户。而是会拆成步骤一、步骤二、步骤三.... 一步一步让用户掉入这个坑完成表单,以增加表单完成的成功率。
要制作的 UI 将拆分成三个表单:
- 第一个表单: 选票种
- 第二个表单: 填姓名、E-mail、电话
- 第三个表单: 填个人网站URL、填自我介绍
其中第二个表单和第三个表单,除了有下一步之外,也可以回到上一步进行修改。如果用户中途离开,下次再进来也可以继续编辑。
另一种纯前端的做法,例如 jQuery Steps,则是只用特效的方式拆成不同步骤,而没有将过程储存进到数据库,如果中离就毫无纪录。本章的做法是中间过程都会存进数据库。
实做简介:
第一步:拆路径;原本的controller是new和create, 现在拆成三部分。
resources :registrations do
# 增加1-3步的url指向contoller#action,并对url使用别名:
member do
get 'steps/1' => "registrations#step1", as: :step1
patch "steps/1/update" => "registrations#step1_update", as: :update_step1
get "steps/2" => "registrations#step2", as: :step2
patch "steps/2/update" => "registrations#step2_update", as: :update_step2
get "steps/3" => "registrations#step3", as: :step3
patch "steps/3/update" => "registrations#step3_update", as: :update_step3
end
end
注意: step1是step2返回到step1的路径,因为第一步是new和create,已经创建了记录就无需再创建了。
第二步:写各个步骤的controller和view。
controller:
def step2
@registration = @event.registrations.find_by_uuid(params[:id])
end
def step2_update
@registration = @event.registrations.find_by_uuid(params[:id])
if @registration.update(registration_params)
redirect_to step3_event_registration_path(@event, @registration)
else
render "step2"
end
end
view:
拆原来的form,注意几点:
- step2的form_for需要加上url参数, url指向的是#step2_update, HTML动作是patch
- 实例变量是form_for的参数
<%= form_for @registration, :url => update_step2_event_registration_path(@event, @registration) do |f| %>
第三步: 写各个步骤的返回上一步功能:
- view的表格底部加上连接link_to,url的HTML动作是get.
- 第2步返回第一步,使用的是自定义的路径step1_event_registration_path(), 这是因为返回到第一步是修改而不是新建,已经创建了记录就无需再创建了,所以需要从新自定义一个返回step1的路径。
第四步: validates,因为拆成几步,所以每步的验证也就不一样了。对每一步编号,然后根据编号来进行验证。
Model层:
- 加上实例变量attr_assessor :current_step
- 对validations_presence_of :name,...,后面加上:if进行判断,if返回true或false,需要定义一个方法。
validates_presence_of :name, :email, :cellphone, if: :should_validate_basic_data?
def should_validate_basic_data?
current_step == 2
end
controller层:
create, step1_update, step2_update, step3_update, 加上@registration.current_step = 1|2|3
显示资料验证错误讯息
服务器验证, 和前端验证两种:
服务器验证:
<div class="form-group <%= (f.object.errors[:name].any?)? "has-error" : "" %>">
<%= f.label :name %>
<%= f.text_field :name, :class => "form-control" %>
<% if f.object.errors[:name] %>
<span class="help-block"><%= safe_join(f.object.errors[:name], ',')%></span>
<% end %>
</div>
has-error和help-block是bootstrap3的类方法。
可以手动实现这个效果, 先对text_filed加上border-color,然后,给<span>加上style: "color: red;"
f.object
指的是这个 form_for
表单的 model 物件,也就是 @registration
f.object.errors[字段名称]
是个数组储存了这个字段的错误讯息
safe_join(arrary, sep=$,)
和Array#join(Sep=$)功能类似,先flatten,然后遍历map每个item,再使用分隔符号join每一个item,然后使用html_tag,让内含的html tag脱逸(escape check),并返回最后的结果:一个大string。
自订义资料验证的错误显示
Model层使用:
valdate :check_something, on: :create
def check_something
if 条件
errors.add(:base, "messages")
end
end
如果错误不是发生在attribute上,则使用:base。
controller上:
可以使用flash.now[:alert] = @registration.errors[:base].join(",")
now代表只在当前页面显示flash
view上:
<% if notice %>
<p class="alert alert-success"><%= notice %></p>
<% end %>
notice是flash的方法,是flash[:notice]的简写。
前端资料验证:
input中添加required特性。即可。
上述的 HTML5 验证是浏览器内建的,如果想要更漂亮的特效,我们可以考虑安装其他前端的套件。
参考 10 jQuery Form Validation Plugins 我们挑一套 Bootstrap Validator 来试试看。
这个前端套件没有包好的 Gem 可以安装,请手动下载 validator.min.js 这个 javascript 档案,放在 vendor/assets/javascripts/
目录下。
然后修改 app/assets/javascripts/application.js
加载它
app/assets/javascripts/application.js
+ //= require validator.min
//= require_tree .
view中:
字段上添加: <div class="help-block with-errors"></div>
javascript代码:
+ <script>
+ $("form").validator();
+ </script>
前端验证是不可靠的,用户只要关闭浏览器的 JavaScript 就可以跳过前端验证。
以防万一,还是需要后端验证的,如果前端验证失效时,至少还可以看到错误讯息。
资料筛选和搜寻
- 资料筛选,单选/多选
- 时间区间筛选
- 资料对比筛选
- 关键字search
- 前台活动状态筛选
资料筛选,单选
需求:点选按钮,根据状态或票种(单选)来从数据库中查询报名人资料。
view:
增加一排按钮,2组按钮,1组是根据status来查询,2组是根据ticket_id来查询。
重点是按钮传递的参数:
- 根据status来传递参数admin_event_registrations_path(status: s)
- 根据ticket_id来传递参数admin_event_registrations_path(ticket_id: t.id)
controller:
根据request参数的不同,来从数据库查询不同的资料。
if params[:status] && Registration::STATUS.include?(params[:status])
return @registrations = @event.registrations.includes(:ticket).where(status: params[:status])
end
if params[:ticket_id]
return @registrations = @event.registrations.includes(:ticket).where(ticket_id: params[:ticket_id])
end
然后进行重构:
第一,把query查询语法放到Model中。scope :by_status, ->(s){ where( status: s )}
第二,在页面的按钮组上,每个按钮上显示查询记录的数量。
- link_to "全部#{@event.registrations.size}", admin_event_registrations_path(@event)
- link_to t(s, scope:"registration.status") + "#{@event.registrations.by_status(s).size}", admin_event_registrations_path(status: s)
第三,凡事点击的按钮,要凹陷下去,通过增加css属性给<a>tag。或者使用bootstrap的active类。
- 在class中添加条件判断:#{(params[:status].blank?)? "active" : "" }
- 或者 #{(params[:status] == s)? "active" : ""}
这个功能目前不管用:也非单选需求下的功能:
目的在于建构按钮超连结的参数。当点了状态再点票种,或是点了票种再点状态时,要同时套用两个参数。
app/helpers/admin/event_registrations_helper.rb
module Admin::EventRegistrationsHelper
+
+ def registration_filters(options)
+ params.permit(:status, :ticket_id).merge(options)
+ end
+
end
筛选资料(多选)
使用核选方框。
实务上,单选和多选的作法不太会混用,所以这里会注解掉上一节的单选接口(用if false去掉)
使用的HTML tag:
1. form_tag(url, method)
2. check_box_tag(name, value, checked="false")
controller中:因为是多项条件的筛选。应当是where内用and, or对参数进行整合的运用。
@registrations = @event.registrations.includes(:ticket).order("id DESC").page(params[:page]).per(20)
if Array(params[:statuses]).any?
@registrations = @registrations.where(:status => params[:statuses])
end
if Array(params[:ticket_ids]).any?
@registrations = @registrations.where(:ticket_id => params[:ticket_ids])
end
见⬆️, 经过2次if的条件筛选得到一个查询语法,如:
SELECT * FROM "registrations" WHERE "registrations"."event_id" = 32 AND "registrations"."status" = 'pending' AND "registrations"."ticket_id" IN (7, 9) ORDER BY id DESC LIMIT 20 OFFSET 0
不理解的是Array(params[..])?
答案:
因为,请求参数中包括"statuses"=>["pending"], "ticket_ids"=>["7", "9"], 它们的值是2个Array,它们是在check_box_tag中定义的name="statuses[]"和name="ticket_ids[]"。
所以,在controller中需要用判断这2个Array值是否存在,使用Array(params[...]), 进一步确保传进来的参数值是数组。
另外,直接写params[...]作为if的条件,来判断是否存在也可以。
时间区间筛选
date_field(object_name, method, options={})
返回一个text_field 类型是date。
options包括 value:"1984-05-11", min: Date.today, max: "2099-10-11"属性。
date_filed_tag(name, value=nil, options={})
options包括,max, min, 和text_field_tag相同的参数如:disabled,
view:
<p>
报名日期:<%= date_field_tag :start_on, params[:start_on] %>~<%= date_field_tag :end_on, params[:end_on] %>
</p>
controller:
if params[:start_on].present?
@registrations = @registrations.where("created_at >= ?", Date.parse(params[:start_on]).beginning_of_day)
end
传递进来的参数是"2018-01-01"的字符串。需要使用parse()转化为日期,然后用beginning_of_day转化为当前设置的时区的时间。
present?看一个对象是否存在。!blank?
资料比对筛选
controller:
if params[:registration_id].present?
@registrations = @registrations.where(:id => params[:registration_id].split(","))
end
view:
text_field_tag :registration_id, params[:registration_id], :placeholder => "报名编号,可用,号区隔", :class => "form-control"
关键字搜寻,使用 Ransack (3900✨)
to search a place very thoroughly, ofen making it untidy.
使用Ransack创建既简单又先进的搜索表格form。支持Rails5.0以上。
ransack 会用数据库的 LIKE 语法来做搜寻,虽然用起来方便,但它会逐笔检查资料是否符合,而不会使用数据库的索引。如果数据量非常多有上万笔以上,搜寻效能就会不满足我们的需要。这时候会改安装专门的全文搜寻引擎,例如 Elasticsearch,这是大数据等级的。
安装即用gem 'ransack',
简单使用(复杂使用没有看)
注意:
- 搜索参数的默认params key是 :q ,
- form_for -> search_form_for, 验证一个Ransack::Search object 会被传给search_form_for
- ActiveRecord::Relation methods不再delegated通过搜索对象。你可以通过使用Ransack#result 搜索结果
在controller中:
@q = Person.ransack(params[:q])
@people = @q.result(distinct: true) #使用distinct:true去掉重复的查询结果。
如果使用关联的表格列:
@q = Person.ransack(params[:q])
@people = @q.result.includes(:articles).page(params[:page]).to_a.uniq
#使用to_a.uniq移除重复,也可以在view中实现。
在view中:
又2个helper方法sort_link,search_form_for
Ransack's search_form_for
helper replaces form_for
for creating the view search form
<%= search_form_for @q, url: admin_event_registrations_path(@event) do |f| %>
<P><%= f.search_field :name_cont, :placeholder => "姓名", class: "form-control"%></p>
<P><%= f.search_field :email_cont, :placeholder => "E-mail", class: "form-control"%></p>
:name_cont中的cont是contains包括,这是search predicates搜索谓语。详见:list , wiki
什么是搜索谓语?
在Ransack搜索中, Predicates用于决定匹配什么信息。例如:cont predicate 会核查是否一个属性包含一个值,通过使用一个wildcard query(通配符查询)。
例子:
> User.ransack(email_cont: "candy@")
=> Ransack::Search<class: User, base: Grouping <conditions: [Condition <attributes: ["email"], predicate: cont, values: ["candy@"]>], combinator: and>>
> User.ransack(email_cont: "candy@").result
User Load (0.2ms) SELECT "users".* FROM "users" WHERE ("users"."email" LIKE '%candy@%')
=> #<ActiveRecord::Relation []>
> User.ransack(email_cont: "candy@").result.to_sql
=> "SELECT \"users\".* FROM \"users\" WHERE (\"users\".\"email\" LIKE '%candy@%')"
可以和or, and 连用:
>> User.ransack(first_name_or_last_name_cont: 'Rya').result.to_sql
=> SELECT "users".* FROM "users" WHERE ("users"."first_name" LIKE '%Rya%'
OR "users"."last_name" LIKE '%Rya%')
也可使用关联,假设User has_one Account, Account 有属性foo, bar:
>> User.ransack(account_foo_or_account_bar: "var").result.to_sql
=> SELECT * FROM "users" INNER JOIN accounts ON account.user_id = users.id WHERE( "accounts.foo LIKE '%var%' OR accounts.bar LIKE '%var%')
注意⚠️:对一个不存在的属性使用a predicate 会失败,where子句相当于不存在。
eq(equals)
eq predicate returns all records where a field is exactly equal to a given value; 相反的有not_eq
matches
匹配查询所有记录并返回, 相反的有does_not_match
使用LIKE 'xxx',精确匹配, 而contain使用 LIKE '%xxx%'
lt (less than)
gt(greater than)
gteq(greater than or equal to)
Iteq(less than or equal to)
in
>> User.ransack(age_in: 20..25)
>> User.ransack(age_in: [20, 21, 22, 23])
上面都是和数字相关的predicate
cont_all(contains all)
city_cont_all: %w(Grand Rapids)必须包括所有关键字才满足条件,生成
WHERE (("users"."city" LIKE '%Grand%' AND "users"."city" LIKE '%Rapids%'))
not_cont_all
cont_any( contains any)
first_name_cont_any: %w(Rya Lis)) 包括任意关键字即可 ,生成
WHERE (("users"."first_name" LIKE '%Rya%' OR "users"."first_name" LIKE '%Lis%'))
not_cont_any
start(starts with)
LIKE "%xx" 开头是xxx, 类似正则表达式/^xxx/
end(ends with)
LIKe "xx%" 结尾是xxx, 类似正则表达式/xxx$/
true , false
The false
predicate returns all records where a field is false.
>> User.ransack(awesome_false: '1').result.to_sql
=> SELECT "users".* FROM "users" WHERE ("users"."awesome" = 'f')
present , blank
>> User.ransack(first_name_present: '1').result.to_sql
=> SELECT "users".* FROM "users" WHERE (("users"."first_name" IS NOT NULL AND "users"."first_name" != ''))
null
>> User.ransack(first_name_null: 1).result.to_sql
=> SELECT "users".* FROM "users" WHERE "users"."first_name" IS NULL
URL parameter structure
Parameters: {"utf8"=>"✓", "q"=>{"name_cont"=>"", "email_cont"=>"cand"}, "registration_id"=>"", "statuses"=>["pending"], "start_on"=>"", "end_on"=>"", "commit"=>"送出筛选", "event_id"=>"hahaha-meetup"}
User.ransack(params[:q]) , 搜索参数被传入到ransack内是一个hash结构。q[:email_count]= "cand"
如果使用JavaScript来创建一个URL, 一个匹配的查询:
$.ajax({
url: "/users.json",
data: {
q: {
first_name_cont: "pete",
last_name_cont: "jack",
s: "created_at desc"
}
},
success: function (data){
console.log(data);
}
});
软删除和版本控制
- 在实际运作的网站中,用户可能会不小心删除资料, 用户可能会透过客服请求管理员进行复原。
- 针对重要的资料,建立追踪和稽核的机制。
软删除(Soft Deletion):不真的删除这一笔资料,常见的作法是增加一个删除的标记字段(例如 deleted_at
字段),如果被标记删除了,那就不要显示出来即可。
版本控管:建立一个 Version Model 来存储编修纪录。如果本来的资料被删除或修改,则会复制资料到这个 Model 去。 使用 paper_trail gem
Paper_trail 一个流行的版本控制gem (5000✨)
- 1.b. Installation
- 1.c. Basic Usage
- 1.d. API Summary (这是各个方法的介绍,具体如何用见3working with versions)
- 1.e. Configuration
当一个类,如Registration在model层加上has_paper_trail,就意味它加入了version control版本控制。
Version数据库根据属性item_type找到Registration, 根据属性item_id找到它的对应记录。
例子:
假设一条记录registration,经过3次数据update。那么在Version中同步insert into 3条记录
INSERT INTO "versions" ("item_type", "item_id", "event", "object", "created_at", "object_changes") VALUES (?, ?, ?, ?, ?, ?)
event字段储存的是create, update, destroy等方法。(也可以客制化event names)
object字段储存未改的记录,
object_changes储存记录更新或改变。
使用registration.versions方法 得到这条记录的所有之前的版本信息,返回一个数组集合,如果没有版本变化返回空数组。
#<ActiveRecord::Associations::CollectionProxy
[#<PaperTrail::Version id: 6, item_type: "Registration", item_id: 1004, event: "update", whodunnit: nil, object: "...", created_at: "...", object_changes: "...">,
#<PaperTrail::Version id: 7, item_type: "Registration", item_id: 1004, event: "update", whodunnit: nil, object: "...", created_at: "...", object_changes: "...">]
>
使用r = registrations.versions.last方法,得到最近一次的version变化。
r.event 等方法获得对应的字段的值。
r.whodunnit 得到current_user的
Rails-Treasure chest3 嵌套表单; Ransack(3900✨)用于模糊查询, ranked-model(800🌟)自订列表顺序; PaperTrail(5000✨)跟踪model's data,auditing and versioning.的更多相关文章
- Rails-Treasure chest2 嵌套表单;
嵌套表单1-1 嵌套表单1-多 选日期时间的UI (一个jquery Plugin) 拆除前后台css和js Rich Editor, 显示输入的HTML tag 批次编辑/删除 嵌套表单1-1 核心 ...
- 对一个表中所有列数据模糊查询adoquery
如何用adoquery对一个表中所有列进行模糊查询: procedure TForm3.Button4Click(Sender: TObject); var ASql,AKey: string; I: ...
- Rails中关联数据表的添加操作(嵌套表单)
很早就听说有Web敏捷开发这回事,最近终于闲了下来,可以利用业余的时间学些新东西,入眼的第一个东东自然是Ruby on Rails.Rails中的核心要素也就是MVC.ORM这些了,因此关于Rails ...
- Oracle 表分组 group by和模糊查询like
分组group by写法 select 字段名 from 表名 group by 字段名 查询这个字段名里的种类分组后可以加聚合函数select 字段名,聚合函数 from 表名 group by 字 ...
- IOS-CoreData(增删改查、表关联、分页和模糊查询、多个数据库)
1>什么是CoreData Core Data是iOS5之后才出现的一个框架,它提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数 ...
- javascript 提取表单元素生成用于提交的对象(序列化 html 表单)
function serialize(f) { var o = {}; var s = f.getElementsByTagName("select"); for (var i = ...
- 【Reporting Services 报表开发】— 怎么根据当前表单的guid作为参数查询相关数据?
select AId from FilteredA as CRMAF_FilteredA 用这个 作为一个DataSet1 , 然后添加在报表里面添加一个参数 @AId,设置的默认的查询为前面Data ...
- selenium——表单嵌套
<html> <iframe id="id-iframe" name="iframee1"> --切换表单 <html> & ...
- Angular 表单嵌套、动态表单
说明: 组件使用了ng-zorro (https://ng.ant.design/docs/introduce/zh) 第一类:嵌套表单 1. 静态表单嵌套 demo.component.html & ...
随机推荐
- Yii 各种url地址写法
echo Url::home(); 生成入口地址/yii2test/frontend/web/index.php: echo Url::base();生成入口文件夹地址:/yii2test/fron ...
- Session的存储原理
一.session是怎么存储,提取的? 1.在服务器端有一个session池,用来存储每个用户提交session中的数据,Session对于每一个客户端(或者说浏览器实例)是“人手一份”,用户首次与W ...
- [.Net]System.OutOfMemoryException异常
1. 一个异常情景 加载15000条等高线,平均每条线有400个点到三维球上,等待时间太长.而且可能会报内存异常. 2. 不错的分析 http://wenku.baidu.com/view/14471 ...
- c primer plus(五版)编程练习-第八章编程练习
1.设计一个程序,统计从输入到文件结尾为止的字符数. #include<stdio.h> int main(void){ int ch; int i; i=; while((ch = ge ...
- 找回 linux root密码的几种方法
第1种方法: 1.在系统进入单用户状态,直接用passwd root去更改 2.用安装光盘引导系统,进行linux rescue状态,将原来/分区挂接上来,作法如下: Java代码 #> c ...
- STL学习笔记--算法
关于STL算法需要注意的是: (1) 所有STL算法被设计用来处理一个或多个迭代器区间.第一个区间通常以起点和终点表示,至于其他区间,多数情况下只需提供起点即可,其终点可自动以第一区间的元素数推导出来 ...
- [golang note] 接口使用
侵入式接口 √ 在其他一些编程语言中,接口主要是作为不同组件之间的契约存在,即规定双方交互的规约. √ 对契约的实现是强制的,即必须确保用户的确实现了该接口,而实现一个接口,需要从该接口继承. √ 如 ...
- C++ Builder创建和调用dll中的资源
程序开发中经常会用到一些图标.图片.光标.声音等,我们称它们为资源(Resource).当多个窗口用到同样的资源时,可以将这些公共的资源放到一个dll文件里调用,这样,由于定位资源比在磁盘中定位文件花 ...
- 26种基于PHP的开源博客系统
26种基于PHP的开源博客系统 来源:本站原创 PHP学习笔记 以下列举的PHP开源Blog系统中,除了我们熟知的WordPress之外,大多都没有使用过,其中一些已经被淘汰,或者有人还在使用.除了做 ...
- FTRL与Online Optimization
1. 背景介绍 最优化求解问题可能是我们在工作中遇到的最多的一类问题了:从已有的数据中提炼出最适合的模型参数,从而对未知的数据进行预测.当我们面对高维高数据量的场景时,常见的批量处理的方式已经显得力不 ...