Rails下单项排序的实现和翻页的改进实现。

Posted in rails | No Comments »

我在之前曾经写过简单翻页的实现:http://daceice.blog.xiezhua.com/?p=5213
这一篇的写法相对比较粗糙,当然这一次的方法也精致不到哪里去。

相较于之前,这次顺带将排序功能一并实现了进去。

归纳总结一下。

rails程序的主要功能可以分为四个:列表,显示,创建,更新。
显示列表的功能,我目前实现的有:列表,翻页,字段检索,名字匹配搜索,排序。

在之前的翻页实现中,我通过hard code将链接地址写出来。
而更好的方法是使用path机制来传递参数。
这样一方面可以避免相对路径产生的问题,另一方面也更加规范化了参数的传递。

以联系人ContactIndividual为例。

首先在view中设置两个链接。
<th>名称
<%=link_to image_tag(”down_arrow.png”,:border=>0), contact_individuals_path(:sort_by=>”name”,:sort_order=>”DESC”)%>
<%=link_to image_tag(”up_arrow.png”,:border=>0), contact_individuals_path(:sort_by=>”name”,:sort_order=>”ASC”)%></th>

其中sort_by和sort_order是排序方式的参数。

为控制器建立before_filter,获取get请求的参数。

def get_sort
@sort_by=”created_at”
@sort_order=”DESC”
if params[:sort_by]!=nil&&params[:sort_by]!=”"
@sort_by=params[:sort_by]
end
if params[:sort_order]!=nil&&params[:sort_order]!=”"
@sort_order=params[:sort_order]
end
end 

当没有参数指定排序方式的时候我们根据联系人的创建日期来排序。


class ContactIndividualsController < ApplicationController

require ‘iconv’

before_filter :get_sort, :only => [:index,:search,:search_name]

……
……
……

def index
@local=20
@count=ContactIndividual.count   
@offset=0
if params[:off]!=nil
@offset=@local*params[:off].to_i
end
if params[:sort_by]==”name”
conv = Iconv.new(”GBK”, “utf-8″)
@contact_individual = ContactIndividual.find(:all).sort {|x, y| conv.iconv(x.name) <=> conv.iconv(y.name)}
@contact_individuals=[]
@end= @offset+@local<=@count-1 ? @offset+@local : @count-1
@contact_individuals=@contact_individual[@offset,@local]
else
@contact_individuals = ContactIndividual.find(:all, :limit=>@local, :offset=>@offset,:order=>”#{@sort_by} #{@sort_order}”)
end
@contacters=Contacter.find(:all,:conditions=>["isindividual==?",false])
@editall=false
if (@action_privilege.include? ['contacter','editable'])||session[:user_id]==1
@editall=true
end
respond_to do |format|
format.html # index.html.erb
format.xml  { render :xml => @contact_individuals }
end
end

……
……
……

end

这里,对于中文文字的排序,例如这里的name,是比较麻烦的操作,如果使用简单的order_by,则达到的效果并不是预想中按照拼音顺序排序的顺序。
为此,我将中文的排序单列出来,使用Iconv进行转译并排序。这样很容易降低排序的效率。
或者可以使用sql语句,find_by_sql而不用find来实现,但是不同数据库的sql语句不标准,需要视数据库类型来写这条语句。

view中的翻页部分如下:

<%@page=2%>
<% params[:off] != nil ? @this=params[:off].to_i : @this=0%>
<%@this+@page > (@count-1)/@local ? @end=(@count-1)/@local : @end=@this+@page%>
<%@this-@page > 0 ? @start=@this-@page : @start=0%>

<%if @this>0%>   
<%=link_to “prev”, contact_individuals_path(:sort_by=>params[:sort_by],:sort_order=>params[:sort_order],:off=>@this-1)%>
<%end%>

<%if @start>0%>   
<%=link_to “1″, contact_individuals_path(:sort_by=>params[:sort_by],:sort_order=>params[:sort_order],:off=>0)%>
<%if @start>1%>…<%end%>
<%end%>

<%@start.upto @end do |i|%>
<%if i==@this%><strong><%=i+1%></strong>
<%else%>
<%=link_to “#{i+1}”, contact_individuals_path(:sort_by=>params[:sort_by],:sort_order=>params[:sort_order],:off=>i)%>
<%end%>
<%end%>

<%if @end<(@count-1)/@local%>
<%if @end+1<(@count-1)/@local%>…<%end%>   
<%=link_to “#{(@count-1)/@local+1}”, contact_individuals_path(:sort_by=>params[:sort_by],:sort_order=>params[:sort_order],:off=>(@count-1)/@local)%>
<%end%>

<%if @this<(@count-1)/@local%>
<%=link_to “next”, contact_individuals_path(:sort_by=>params[:sort_by],:sort_order=>params[:sort_order],:off=>@this+1)%>
<%end%>

只可意会的Rails权限控制——2、控制器

Posted in rails | No Comments »

一、新建和更新权限

class RolesController < ApplicationController

def create
@role = Role.new(params[:role])
respond_to do |format|
if @role.save
flash[:notice] = ‘成功创建用户分类’
format.html { redirect_to(@role) }
format.xml  { render :xml => @role, :status => :created, :location => @role }
else
flash[:error] = ‘创建用户分类失败’
@role = Role.new
@privileges= Privilege.find(:all,:order=>”p_object DESC”)
format.html { render :action => “new” }
format.xml  { render :xml => @role.errors, :status => :unprocessable_entity }
end
end
end

def update
@role = Role.find(params[:id])
respond_to do |format|
if params[:role]["privilege_ids"]==nil
params[:role]["privilege_ids"]=[]
end
if @role.update_attributes(params[:role])
flash[:notice] = ‘成功更新用户分类’
format.html { redirect_to(@role) }
format.xml  { head :ok }
else
flash[:error] = ‘更新用户分类失败’
@role = Role.new
@privileges= Privilege.find(:all,:order=>”p_object DESC”)
format.html { render :action => “edit” }
format.xml  { render :xml => @role.errors, :status => :unprocessable_entity }
end
end
end

二、权限控制

权限控制的实现大多通过before_filter实现。
作为权限判断的基础,在登陆时需要用session记录下一些字段。

class SessionsController < ApplicationController

def create
@current_user = User.find_by_name_and_password(params[:login], params[:password])
if @current_user
session[:user_id] = @current_user.id
redirect_to index_path
else
render :action => ‘new’
end
end
end

1、本次操作的判断

class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery # See ActionController::RequestForgeryProtection for details

before_filter :fetch_logged_in_user

……
……
……
# 在任何操作之前,根据session[:user_id]获取当前用户信息
def fetch_logged_in_user
if session[:user_id]==nil
redirect_to root_path
else
@current_user = User.find_by_id(session[:user_id])
end
@r_controller = params[:controller]
@r_action = params[:action]
if @r_controller!=’sessions’ && @r_controller!=’index’
valid = false
privileges=@current_user.role.privileges
privileges.each do |p|
if p.valid_request?(@r_controller, @r_action)
valid=true
end
end
end
if valid==false && @current_user.id!=1
redirect_to :controller=>’index’, :action=>’error’
end
valid = true   
end
……
……
……
end

2、目录结构的生成

这里其实时view中的实现。

【./index/left.html.erb】

<%privileges=@current_user.role.privileges%>
<%object_privilege=[]%>
<%action_privilege=[]%>
<%privileges.each do |p|%>
<%object_privilege<<p.p_object%>
<%action_privilege<<[p.p_object,p.p_action]%>
<%end%>

……
……
……

<%if (object_privilege.include? ‘order_list’) || (object_privilege.include? ‘out_list’) || (object_privilege.include? ‘apply_process’)%>

<h2>申请管理</h2>

<div>

<%if object_privilege.include? ‘apply_process’%>
<%=link_to ‘申请流程设置’,apply_processes_path, :target=>”mainFrame”%><br>
<%end%>

<%if (object_privilege.include? ‘order_list’ )&&( action_privilege.include? ['order_list','readall'])%>
<%=link_to ‘采购申请’,purchase_orders_path, :target=>”mainFrame”%><br>
<%=link_to ‘报销申请’,reimbursement_orders_path, :target=>”mainFrame”%><br>   
<!–
<%=link_to ‘设备报销申请’,index_e_reimbursement_orders_path, :target=>”mainFrame”%><br>–>
<%=link_to ‘供应商付款申请’,vendor_orders_path, :target=>”mainFrame”%><br>
<%=link_to ‘工程队付款申请’,corp_orders_path, :target=>”mainFrame”%><br>
<%end%>

<%if (object_privilege.include? ‘out_list’) && (action_privilege.include? ['out_list','readall'])%>
<%=link_to ‘出库申请’,list_out_lists_path, :target=>”mainFrame”%><br>   
<%end%>   
</div>
<%end%>


<%if (object_privilege.include? ‘order_list’) || (object_privilege.include? ‘out_list’) %>

<h2>我的申请列表</h2>

<div>
<%if (object_privilege.include? ‘order_list’)&& (action_privilege.include? ['order_list','readable'])%>
<%=link_to ‘采购申请’,show_my_purchase_orders_path, :target=>”mainFrame”%><br>
<%=link_to ‘报销申请’,show_my_reimbursement_orders_path, :target=>”mainFrame”%><br>   
<!–
<%=link_to ‘设备报销申请’,show_my_e_reimbursement_orders_path, :target=>”mainFrame”%><br>–>
<%=link_to ‘供应商付款申请’,show_my_vendor_orders_path, :target=>”mainFrame”%><br>
<%=link_to ‘工程队付款申请’,show_my_corp_orders_path, :target=>”mainFrame”%><br>
<%end%>
<%if (object_privilege.include? ‘out_list’)&& (action_privilege.include? ['out_list','readable'])%>
<%=link_to ‘出库申请’,out_lists_path, :target=>”mainFrame”%><br>
<%end%>       
</div>
<%end%>

……
……
……

2、个别按钮或链接的控制

class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery # See ActionController::RequestForgeryProtection for details

before_filter :fetch_logged_in_user

……
……
……

def validate_action_to_edit
privileges=@current_user.role.privileges
@action_privilege=[]
privileges.each do |p|
@action_privilege<<[p.p_object,p.p_action]
end
end
……
……
……

【./users/index.html.erb】

……
……
……
<%if @editall==true%>
<br><hr width=500 align=left>

<%= link_to ‘新建’, new_user_path %>
<%end%>

只可意会的Rails权限控制——1、模型和插件

Posted in rails | No Comments »

一、模型

我所写的权限控制主要涉及三个模型:用户(User),角色(Role)和权限(Privilege)
三者之间的关系是:
用户——角色:多对一(或多对多)
角色——权限:多对多

简单起见,这里只实现用户——角色为多对一关系的权限控制。

首先为这些模型建立表,相关的migrate文件如下:

class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :name
t.string :password
t.integer :role_id

t.timestamps
end
end

def self.down
drop_table :users
end
end

用户的信息必须包括用户名,用户密码,用户的角色id,此外还有用户id和时间戳

class CreateRoles < ActiveRecord::Migration
def self.up
create_table :roles do |t|
t.string :name

t.timestamps
end
end

def self.down
drop_table :roles
end
end

角色的信息必须包括角色名,此外还有角色id和时间戳

class CreatePrivileges < ActiveRecord::Migration
def self.up
create_table :privileges do |t|
t.string :p_object
t.string :p_action

t.timestamps
end
end

def self.down
drop_table :privileges
end
end

权限的信息必须包括权限的操作对象和权限的操作类型,此外还有权限id和时间戳

为了实现角色和权限的多对多关系,我们还需要建立一个将两者关联的表

class CreatePrivilegeRoles < ActiveRecord::Migration
def self.up
create_table :privilege_roles, :id => false do |t|
t.string :privilege_id
t.string :role_id

t.timestamps
end
end

def self.down
drop_table :privilege_roles
end
end

特别要注意的是 :id => false 的语句,使这个表的每条记录不存在id,否则会出错。

随后在modol中将对应关系补全如下:

class User < ActiveRecord::Base
belongs_to:role
validates_presence_of :name,:role_id,:password
validates_uniqueness_of :name
end

class Role < ActiveRecord::Base
has_many :users
validates_presence_of :name
validates_uniqueness_of :name
has_and_belongs_to_many :privileges,
:join_table => ‘privilege_roles’,
:association_foreign_key => ‘privilege_id’,
:foreign_key => ‘role_id’
end

class Privilege < ActiveRecord::Base
@@all_privileges = {
……
……
……
‘product’=>{
‘readable’=>[
['products','index'],
['products','search'],
['products','search_name'],
['products','show'],
['product_categories','index'],
['product_categories','search'],
['product_categories','search_name'],
['product_categories','show'],
['products','search_product_by_category']
],
‘editable’=>[
['products','new'],
['products','new_reim'],
['products','new_corp'],
['products','new_vendor'],
['products','edit'],
['products','create'],
['products','update'],
['products','destroy'],
['product_categories','edit'],
['product_categories','create'],
['product_categories','new'],
['product_categories','destroy'],
['product_categories','update']
]
},
……
……
……
}

@@privilege_name = {
‘index’=>’首页’,
‘contacter’=> ‘联系人’,
‘product’=>’基础资料’,
‘apply_process’=>’申请流程’,
‘order_list’=>’费用申请’,
‘out_list’=>’出库申请’,
‘order_apply’=>’批复费用申请’,
‘out_apply’=>’批复出库申请’,
‘project’=>’项目’,
‘final’=>’结算’,
’storage’=>’库存’,
’storage_flow’=>’库存操作’,
‘blog’=>’工作计划’,
‘account’=>’应收应付’,
‘user’=>’用户’,
‘log’=>’系统日志’,
‘notice’=>’通知’
}

@@privilege_type = {
‘applyable’=>’可申请’,
‘readable’=>’可察看’,
‘editable’=>’可新建/修改’,
‘readall’=>’可察看所有’,
‘editall’=>’可新建/修改所有’
}

def self.privilege_name
@@privilege_name
end

def self.privilege_type
@@privilege_type
end

def self.all_privileges
@@all_privileges
end

def valid_request?(r_controller, r_action)
if @@all_privileges[p_object][p_action].include? [r_controller,r_action]
return true
else
return false
end
end 
end

在privilege模型中包含这样几个常量
@@all_privileges是权限控制的主要常量。其第一层hash的key是权限操作的对象,第二层key是操作的类型,第三层是一组由二项数组的权限对组成的数组,这组数组包括了对于一种操作对象的一种操作类型所涉及的controller和action组的集合。
privilège的记录字段来自这个常量,其第一个字段p_object为第一层hash的key,其第二个字段p_action为第二层hash的key。
@@privilege_type用于友好显示操作类型的名称。
@@privilege_name用于友好显示操作对象的名称。
def self.privilege_name等函数使常量可以通过Privilege.privilege_name等调用可用。
def valid_request?(r_controller, r_action)函数用来验证递交的权限组是否符合当前权限要求。

privilege的字段必须在程序开始使用之前预设并覆盖所有权限hash,并不得修改。
如果修改了@@all_privileges常量中第一或第二层的值的时候必须相应修改privilege表。
修改@@all_privileges常量权限对的微调不需要修改privilege表。

初始化使用如下形式的文件:test.rb

#!/usr/bin/env ruby

all_privileges = {
……
……
……
‘product’=>{
‘readable’=>[
['products','index'],
['products','search'],
['products','search_name'],
['products','show'],
['product_categories','index'],
['product_categories','search'],
['product_categories','search_name'],
['product_categories','show']
],
‘editable’=>[
['products','new'],
['products','new_reim'],
['products','new_corp'],
['products','new_vendor'],
['products','edit'],
['products','create'],
['products','update'],
['products','destroy'],
['product_categories','edit'],
['product_categories','create'],
['product_categories','new'],
['product_categories','destroy'],
['product_categories','update']
]
},
……
……
……
}

a = all_privileges

a.keys.sort.each {|key|
a[key].keys.sort.each {|subkey|
puts “insert into privileges (p_object, p_action)values(’#{key}’, ‘#{subkey}’);”;
}
}

将@@all_privileges覆盖相应位置,运行 ruby test.rb > sql.txt,即可生成sql.txt,内容形如:


……
……
……
insert into privileges (p_object, p_action)values(’product’, ‘editable’);
insert into privileges (p_object, p_action)values(’product’, ‘readable’);
……
……
……

黏贴执行这些sql语句,完成数据库初始化。


二、插件

Role与Privilege的多对多关系通过Role的privilege_ids函数进行控制。
privilege_ids函数表现为一个包含Role关联的Privilege的id的数组。
对该数组赋值并save可以使Role和关联Privilege建立关系。

这里推荐插件SwapSelect来实现view。
下载及使用说明在:http://trendwork.kmf.de/175

其在view中的使用方法如下:

【./roles/new.html.erb】

<h1>新建用户分类</h1>
<p>
<% form_for(@role) do |f| %>
<%= f.error_messages %>
名称:<%=f.text_field :name%>
</p>
<p>
权限列表:
<table width=350><tr><td><%= swapselect :role, @role, :privileges, @privileges.collect {|p| [Privilege.privilege_type["#{p.p_action}"]+Privilege.privilege_name["#{p.p_object}"], p.id] } ,{:size => 10}%></td></tr></table></p>
<p>
<%= f.submit ‘创建’ %>
</p>
<% end %>

当递交后,传递的参数为:
Parameters: {”commit”=>”创建”, “authenticity_token”=>”S4XeRpC/3QAp0Vt3wx+IMMpnXLvrEUiECmbCqapewb8=”, “role”=>{”name”=>”角色名称”, “privilege_ids”=>["40"]}}

需要注意的是,当没有选择权限的时候privilege_ids参数项不被传递,这在新建的时候没有问题,而在,更新的时候如果将权限置空,则无法更新权限列表。

rail中带省略号的分页的简单实现

Posted in 晒物 | No Comments »

控制器部分可参考:http://daceice.blog.xiezhua.com/?p=5213

<%@page=2%>

#当前页前后浮动的页码

<% params[:off] != nil ? @this=params[:off].to_i : @this=0%>

#获取当前页码

<%@this+@page > (@count-1)/@local ? @end=(@count-1)/@local : @end=@this+@page%>

#获取连续页码头

<%@this-@page > 0 ? @start=@this-@page : @start=0%>

#获取连续页码尾

<%if @this>0%>

<%@path_prev=”../users?off=#{@this-1}”%>

<%=link_to ‘prev’, “#{@path_prev}”%>

<%end%>

#是否有前一页

<%if @start>0%>

<%@first_path=”../users?off=0″%>

<%=link_to “1″, “#{@first_path}”%>

<%if @start>1%>…<%end%>

<%end%>

#第一页页码

<%@start.upto @end do |i|%>

<%if i==@this%><strong><%=i+1%></strong>

<%else%>

<%@num_path=”../users?off=#{i}”%>

<%=link_to “#{i+1}”, “#{@num_path}”%>

<%end%>

<%end%>

#连续页码,当前也无链接加粗

<%if @end<(@count-1)/@local%>

<%if @end+1<(@count-1)/@local%>…<%end%>

<%@path_last=”../users?off=#{(@count-1)/@local}”%>

<%=link_to “#{(@count-1)/@local+1}”, “#{@path_last}”%>

<%end%>

#最后页页码

<%if @this<(@count-1)/@local%>

<%@path_next=”../users?off=#{@this+1}”%>

<%=link_to ‘next’, “#{@path_next}”%>

<%end%>

#是否有下一页

rails中简单分页、检索、关键字搜索的实现

Posted in rails | No Comments »

分页

controller

@local=5
#每页显示数量
@count=User.count
#总数量 
@offset=0
#初始偏移量,总便宜量为@offset*@local
if params[:off]!=nil
@offset=@local*params[:off].to_i
#根据参数修改偏移量
end
@users = User.find(:all, :limit=>@local, :offset=>@offset)
#根据显示数目和偏移量从数据库中读取。

需要注意的是@offset从0开始,@count从1开始。

view

<%if @offset!=0%>
<%@path_prev=”../users?off=#{@offset/@local-1}”%>
<%=link_to ‘prev’, “#{@path_prev}”%>
<%end%>
<%0.upto((@count-1)/@local) do |i|%>
<%@num_path=”../users?off=#{i}”%>
<%=link_to “#{i+1}”, “#{@num_path}”%>
<%end%>
<%if @offset/@local!=(@count-1)/@local%>
<%@path_next=”../users?off=#{@offset/@local+1}”%>
<%=link_to ‘next’, “#{@path_next}”%>
<%end%>

检索

view

<h1>用户列表</h1>
<% form_tag search_users_path, :method => :get do -%>
<div id=”search”>
用户分类列表:
<%=select_tag(”q”, options_for_select( @roles.collect {|r| [ r.name, r.id ] })) %>
<%= submit_tag ‘查看’, :class => ‘button’ %>
</div>
<% end -%>

controller

@local=5
@count=User.count(:conditions=>["role_id==?",params[:q]])  
@offset=0
if params[:off]!=nil
@offset=@local*params[:off].to_i
end
@users = User.find(:all, :limit=>@local, :offset=>@offset, :conditions=>["role_id==?",params[:q]])

关键字搜索

view

<% form_tag search_name_users_path, :method => :get do -%>
<div id=”search”>
<%=text_field_tag “q”%>
<%= submit_tag ‘查找’, :class => ‘button’ %>
</div>
<% end -%>

controller

@local=5  
@offset=0
if params[:off]!=nil
@offset=@local*params[:off].to_i
end
@count=User.count(:conditions=>["name like ?","%#{params[:q]}%”])  
@users = User.find(:all, :limit=>@local, :offset=>@offset, :conditions=>["name like ?","%#{params[:q]}%”])