Rails-Treasure chest1 (自定义Model网址;多语言包; 时区设置, TimeZone类; 格式日期时间; 表单单选UI; 表单多选UI;Select2 Plugin)
- 自定义Model网址: 随机值网址SecureRandom.base58
- 多语言包, 包括默认语言设置和user自设置。
- 时区设置, TimeZone类 ,增加user自选时区功能
- 格式日期时间: xx.created_at.strftime("%Y-%m-%d %H:%M")
- 表单单选UI
- 表单单选UI和Select2 Plugin
- 表单多选UI和Select2 Plugin
自定义Model网址
- 方案一:网址上除了数字ID,可以再加上文字
- 方案二:不要用数据库的递增数字ID,而是用一个随机值产生的 ID✅
- 方案二(改):除了用乱数ID,也可以让用户自定义 ID(在二的基础上修改)
方案一:
在model层使用to_param(*args)方法,别名 ActionController::Parameters#to_query
返回一个接收者的string形式 ,适合用在一个URL 查询字符串。
def to_param
self.id #这是Rails默认形式
end
这是Rails默认的路径方法,会默认添加到event_path(@event)
等同于 event_path(@event.to_param),
因此to_params可以改成
"#{self.id}-#{self.name}", 返回一个"string",
返回结果作为参数"id=xxxxxx", url生成Parameters: {"id"=>"xxxxxx"}
to_query方法原理:
> params = ActionController::Parameters.new({
> name: "David",
> nationality: "Danish"
> })
=> <ActionController::Parameters {"name"=>"David", "nationality"=>"Danish"} permitted: >
> params.to_query
=> "name=David&nationality=Danish"
⚠️: Event.find(params[:id])中的参数会调用to_i,因此后面的非数字会被自动除去。如果写成"#{self.name}-#{self.id}", 就会报错了。因为"Smokey-1"返回的是0
方案二: 随机数✅✅✅
例子url: http://localhost:3000/events/FNRLRNgbKZWfVP7P
1. 新增一个字段friendly_id,带index, 是unique。
add_column :events, :friendly_id, :string
add_index :events, :friendly_id, :unique => true
2. 使用to_param方法: self.friendly_id
3. 所有相关Controller action 改用 @event = Event.find_by(friendly_id: params[:id])
4. model.rb中,增加一个before_validation :generate_XXX, on:[:create], 然后设置这个方法为friendly_id ||= SecureRandom.base58
方案二(增加自定义)
<div class="form-group">
<%= f.label :friendly_id %>
<%= f.text_field :friendly_id, :required => true, class: "form-control"%>
</div>
⚠️ input-Attribute: required代表必须填写。
配置中文语系
如果我们的网站不需要支援多国语系,你可能会觉得这样做有点辛苦,直接将中文写在样板上就好了。
但这个功能对于团队协作开发网站仍然非常有帮助,因为写程式的时候不一定会先确定文案规格,用 I18n 来处理的话,最后只需要让 PM 统一修改翻译词汇档即可。
gem 'rails-i18n' , gem 'devise-i18n'
使用i18n的Rails指导:https://guides.rubyonrails.org/i18n.html
方法:
1. config/application.rb
class Application < Rails::Application
config.i18n.default_locale = "zh-CN"
end
2. 新增 config/locales/zh-CN.yml
"zh-CN": event_list: 活动列表 admin: event_list: 活动列表管理
3. 修改en.yml
"en": + event_list: Event List + admin: event_list: Admin Event List
4. 修改view中对应的位置,统统改为t("event_list")和t("admin.event_list")
t是i18n中的方法。
嵌套variable, 使用%{variable_name}
en:
hello: "HI, %{name}"
view中 使用 t("hello", :name => variable_name )
使用gem 'i18n'后:
config.i18n.default_locale = "zh-CN"
config.i18n.available_locales = ["zh-CN", :en,...] 选择加载的locale包, 不写这句就是全加载。
Devise 也有用到 I18n,可以安装 devise-i18n 这个 gem 有开源社区做好的中文翻译:
https://github.com/tigrish/devise-i18n
对model的字段进行翻译:
zh-CN:
activerecord:
attributes:
event:
name: "活动名称"
description: "描述"
用户自行切换多语言:
app/views/layouts/application.html.erb
+ <%= link_to "中文版", :locale => "zh-CN", :is => "dd" %>
+ <%= link_to "English", :locale => "en" %>
注意:locale是自行传入url的参数 http://localhost:3000/events?locale=zh-CN&ls=dd
app/controllers/application_controller.rb
+ before_action :set_locale
+
+ def set_locale
+ if params[:locale] && I18n.available_locales.include?( params[:locale].to_sym )
+ session[:locale] = params[:locale]
+ end
+
+ I18n.locale = session[:locale] || I18n.default_locale
+ end
语言系样板:
如果样板内大多是属于较为静态的内容,Rails 也提供了不同语系可以有不同样板,你只要将样板命名加上语系附档名即可
执行 rails g controller pages
编辑 config/routes.rb
get "/faq" => "pages#faq"
新增 app/views/pages/faq.zh-CN.html.erb
新增 app/views/pages/faq.en.html.erb
如此在英文版的时候就会使用 faq.en.html.erb
这个样板,中文版时使用 faq.zh-CN.html.erb
这个样板。
最后,编辑 app/views/layouts/application.rb
放上 FAQ 页面的连结:
app/views/layouts/application.rb
<%= yield %>
<%= link_to "FAQ", faq_path %>
表单单选UI(固定选项,无Model)
情况:在表单上加一个单选UI, 选项是固定的几个,因此无需Model来存选项。
首先:新增一个字段来存储选项的状态。使用string.
- rails g migration AddStatusToEvent status:string
- 在model层上,定义一个常量数组,用于储存选项类型,并validates :status, inclusion: STATUS
然后:把数据库的status, 转化为user可看的中文。用Helper方法转化中文,或用i18n转化。
再后:在views/../_form.html.erb中增加select下拉菜单。在controller中,设置params白名单。
- 分支:少量选项可改用Radio Button UI: radio_button()
⚠️:可以加bootstrap4的美化。
表单的select的写法:
f.select :status, Event::STATUS.map{ |s| [t(s, :scope => "event.status"), s] }, {}, :class => "form-control"
表单的radio的写法:radion_button(method, tag_value, options = {} )
<div class="btn-group" data-toggle="buttons">
<% Event::STATUS.each do |status| %>
<label class="btn btn-default <%= (status == f.object.status)? 'active' : '' %>">
<%= f.radio_button :status, status %>
<%= t(status, :scope => "event.status")%>
</label>
<% end %>
</div>
=> <input type="radio" value="draft" name="event[status] id="event_status_draft">
⚠️:
- button样式可以用在其他元素上如<label>, 为了提供checkbox/radio按钮button样式,需要加上data-toggle="buttons" 到一个有.btn-group的元素。目的是给按钮加上$().button('toggle')这个jquery方法,这个方法会给button合适的外观。
- CSS的active, 代表选择的button, 需要developer手动添加到<label>
- f是<FormBuilder:0x0007...>表格对象; f.object是<Event::0x0007...>; f.object.status是当前event的状态。
- 如果status和event的status相同,则选中这个button. 当进入edit页面时,默认就是原先选择的状态。
i18n的写法:
t是translate(key, options={})的简写:
Lookup:
key可以是单独的key,也可以是a dot-separated key(string和symbols都可)
如:t('date.formats.short')
scope可以是单独的key, a dot-separated key或者an array of keys or dot-separated keys.
如:
I18n.t 'formats.short', :scope => 'date'
I18n.t 'short', :scope => 'date.formats'
I18n.t 'short', :scope => %w(date formats)
t(@event.status, :scope => "event.status")
在zh-CN.yml中:
event:
status:
draft: 草稿
private: 私密
public: 公开
插入interpolation:
view中使用 t("hello", :name => variable_name )
在en.yml中
en:
hello: "HI, %{name}"
表单单选 UI 和 Select2 Plugin
例子:Event需要被分类,但这个分类是可以编辑的,非固定的,因此需要新增一个Category Model。
- rails g model category name; Event增加一个category_id,加上index; 建立双方关联。
- 建立Category的 controller: rails g controller admin::categories
- 后台编辑页面增加一个目录select。对应请求参数加入白名单(controller#event_params)
- 前台show页面,显示分类。或者其他外观布景。
- gem 'select2-rails'是一个jQuery Plugin让select菜单看起来非常漂亮。
注意:
第四步,前台页面显示分类时,如果event没有分类会报告nilClass, 需要添加判断或者使用try()方法。
如:@event.category.try(:name)
表单多选UI
例子:给admin后台的user编组
步骤:
- 新建Group用于给users分组
- User和Group是多对多关系。新建一个关联Model Memebership
- 控制台输入一些group资料。
- 多选UI,需要使用checkbox. helper方法可以使用collection_check_boxes()
- controller#user_params的白名单,:group_ids => []
- 前台页面修改。为了避免N+1 query可以使用User.includes(:groups).all
- 也可以使用select 2插件
详解:
第四步,在edit页面,新增checkbox,
<%= f.label :group_ids %>
<%= f.collection_check_boxes(:group_ids, Group.all, :id, :name )%>
会生成原生html:(下面是部分代码)
<input type="hidden" name="user[group_ids][]" value="">
<input type="checkbox" value="1" name="user[group_ids][]" id="user_group_ids_1">
@user的group_ids,指它的一组group_id集合,作为参数传入controller处理,以此生成membership记录。
collection_check_boxes(method, collection, value_method, text_method, options = {}, &block)
method是 :group_ids 一个关联方法。
collection是 Group.all
value_method是 要存入的值, 因此这里是:id (group的id)
text_name是显示的文本内容, 因此这里是 :name (group的name)
这是生成的参数:
Parameters: { "utf8"=>"✓",
"authenticity_token"=>"fyyJ9Q...",
"user"=>{"email"=>"admin@example.org", "group_ids"=>["", "1", "2"]},
"commit"=>"Update",
"id"=>"1"}
调用Admin::UsersController#update方法
#查找user.id是1的记录:@user = User.find(params[:Id])
SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
#然后 @user = @user.update(user_params)
#根据参数中"group_ids"=>["", "1", "2"]查询group.id是1, 2的记录
SELECT "groups".* FROM "groups" WHERE "groups"."id" IN (1, 2)
#membership和group表格连接,然后找user_id = 1的数据,没有找到:
SELECT "groups".* FROM "groups" INNER JOIN "memberships" ON "groups"."id" = "memberships"."group_id" WHERE "memberships"."user_id" = ? [["user_id", 1]]
#因此,根据group_id和user_id, 插入2条memberships
INSERT INTO "memberships" ("user_id", "group_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["user_id", 1], ["group_id", 1], 。。。
INSERT INTO "memberships" ("user_id", "group_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["user_id", 1], ["group_id", 2], 。。。