应用Rails+Ext开发企业级权限平台
一般说来做企业级权限平台都是java的天下,我也不得不认同java这方面的能力。本文介绍的Ext + Rails 开发的企业级权限平台也是在原先用java实现的一套改写而成,保证了基本功能的一致性,UI界面的一致性。这样就充分说明了富客户端框架(或者说RIA)的应用广泛性,能够和多种服务器端语言组合开发应用系统平台。
该平台是基于角色的权限管理后台.集中了一般企业应用所必需包含的必须模块,包括用户维护、模块维护、角色管理、权限管理、数据字典以及统一的页面风格等.由于该开发平台的存在,大大提高了开发人员的开发效率,特别是单表操作,仅仅只需要在范例模块的基础之上进行少量修改即可达到目的.平台架构设计本着追求层次间的低耦合,层次明显分开,TDD开发模式原则进行.
下面有实现的图样~~ (貌似有蛮多 呵呵)
整个开发中没什么新颖的东西 就如同java一般从数据库中去数字组装成符合要求的json字符串输出渲染页面让Ext来显示数据和如何组织项目的结构。
数据库表结构db/schema.rb
字典类型对象和字典对象一对多关系
model/catalog.rb
字典对象
model/dict
组织对象,组织和人员是多对多关系.
model/group.rb
日志对象
model/log.rb
模块对象 模块对象和操作对象都属于资源对象
model/mod.rb
操作对象 模块对象和操作对象都属于资源对象
model/operation.rb
人员组织中间表model
model/person2group.rb
人员资源中间model
model/person2resource.rb
人员角色中间model
model/person2role.rb
人员对象 和组织 角色 资源等关联
model/person.rb
资源对象 包含两种资源 操作和模块
model/resource.rb
角色和资源的关联的中间model
model/role2resource.rb
角色对象 和人员 资源关联
model/role.rb
下面是自己改写过的生成json的插件jsonifier
用于把对象和对象集合转换成符合EXT接受的单个对象的json格式和多个树形结构的json格式
ps======================================== 下面后续的说明其整个项目组织结构和开发中遇到的问题
一般说来做企业级权限平台都是java的天下,我也不得不认同java这方面的能力。本文介绍的Ext + Rails 开发的企业级权限平台也是在原先用java实现的一套改写而成,保证了基本功能的一致性,UI界面的一致性。这样就充分说明了富客户端框架(或者说RIA)的应用广泛性,能够和多种服务器端语言组合开发应用系统平台。
该平台是基于角色的权限管理后台.集中了一般企业应用所必需包含的必须模块,包括用户维护、模块维护、角色管理、权限管理、数据字典以及统一的页面风格等.由于该开发平台的存在,大大提高了开发人员的开发效率,特别是单表操作,仅仅只需要在范例模块的基础之上进行少量修改即可达到目的.平台架构设计本着追求层次间的低耦合,层次明显分开,TDD开发模式原则进行.
下面有实现的图样~~ (貌似有蛮多 呵呵)
整个开发中没什么新颖的东西 就如同java一般从数据库中去数字组装成符合要求的json字符串输出渲染页面让Ext来显示数据和如何组织项目的结构。
数据库表结构db/schema.rb
# This file is auto-generated from the current state of the database. Instead of editing this file,
# please use the migrations feature of ActiveRecord to incrementally modify your database, and
# then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
# to create the application database on another system, you should be using db:schema:load, not running
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 15) do
create_table "catalogs", :force => true do |t|
t.string "catalogname", :limit => 32
t.text "remark"
t.integer "sortno"
t.integer "parent_id"
end
create_table "dicts", :force => true do |t|
t.string "key", :limit => 32, :default => "", :null => false
t.string "value", :limit => 32
t.text "remark"
t.integer "sortno"
t.integer "catalog_id"
end
create_table "groups", :force => true do |t|
t.string "createby", :limit => 32
t.string "name", :limit => 50, :default => "", :null => false
t.text "remark"
t.integer "sortno"
t.integer "parent_id"
end
create_table "logs", :force => true do |t|
t.text "log", :default => "", :null => false
t.string "type", :limit => 32, :default => "", :null => false
t.string "personloginname", :limit => 32
t.string "personname", :limit => 32
t.datetime "dt"
end
create_table "mods", :force => true do |t|
t.text "link"
t.string "type", :limit => 32
t.text "icon"
end
create_table "operations", :force => true do |t|
t.string "sn", :limit => 32
t.string "icon", :limit => 32
t.string "tip", :limit => 32
t.boolean "show_text"
t.boolean "admin_op"
end
create_table "people", :force => true do |t|
t.string "login_name", :limit => 32
t.string "status", :limit => 32
t.string "hased_password"
t.string "salt"
t.string "name", :limit => 32
t.string "sex", :limit => 10
t.date "birthday"
t.integer "sortno"
t.text "config"
t.string "creator", :limit => 32
t.date "createdt"
end
create_table "person2groups", :force => true do |t|
t.integer "person_id"
t.integer "group_id"
t.boolean "isadmin"
t.string "indicator", :limit => 32
end
create_table "person2resources", :force => true do |t|
t.integer "resource_id"
t.integer "person_id"
t.string "indicator", :limit => 32
end
create_table "person2roles", :force => true do |t|
t.string "indicator", :limit => 32
t.integer "person_id"
t.integer "role_id"
end
create_table "resources", :force => true do |t|
t.string "name", :limit => 32
t.integer "sortno"
t.boolean "visiabled"
t.text "remark"
t.integer "parent_id"
t.integer "ph_id"
t.string "ph_type"
end
create_table "role2resources", :force => true do |t|
t.integer "role_id"
t.integer "resource_id"
t.boolean "communicable"
t.boolean "inherit"
end
create_table "roles", :force => true do |t|
t.string "creator", :limit => 32
t.boolean "inheritable"
t.string "name", :limit => 32
t.text "remark"
t.integer "sortno"
t.integer "parent_id"
end
create_table "sessions", :force => true do |t|
t.string "session_id", :default => "", :null => false
t.text "data"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
end
字典类型对象和字典对象一对多关系
model/catalog.rb
class Catalog < ActiveRecord::Base
acts_as_tree :order => "catalogname"
has_many :dicts, :dependent => :destroy
def expanded
true
end
alias_attribute :text, :catalogname
alias_attribute :catalogName, :catalogname
alias_attribute :sortNo, :sortno
#以下省略.....
end
字典对象
model/dict
class Dict < ActiveRecord::Base
belongs_to :catalog
alias_attribute :sortNo, :sortno
end
组织对象,组织和人员是多对多关系.
model/group.rb
class Group < ActiveRecord::Base
acts_as_tree :order => "name"
has_many :person2groups, :dependent => :destroy
has_many :people, :through => :person2groups
alias_attribute :sortNo, :sortno
alias_attribute :groupName, :name
#以下省略.....
end
日志对象
model/log.rb
class Log < ActiveRecord::Base
end
模块对象 模块对象和操作对象都属于资源对象
model/mod.rb
class Mod < ActiveRecord::Base
has_one :resource ,:as => :ph
end
操作对象 模块对象和操作对象都属于资源对象
model/operation.rb
class Operation < ActiveRecord::Base
has_one :resource ,:as => :ph
end
人员组织中间表model
model/person2group.rb
class Person2group < ActiveRecord::Base
belongs_to :person
belongs_to :group
alias_attribute :admin, :isadmin
#以下方法省略......
end
人员资源中间model
model/person2resource.rb
class Person2resource < ActiveRecord::Base
belongs_to :person
belongs_to :resource
#以下方法省略......
end
人员角色中间model
model/person2role.rb
class Person2role < ActiveRecord::Base
belongs_to :person
belongs_to :role
#以下方法省略......
end
人员对象 和组织 角色 资源等关联
model/person.rb
require 'digest/sha1'
class Person < ActiveRecord::Base
has_many :person2groups, :dependent => :destroy
has_many :groups, :through => :person2groups
has_many :person2roles, :dependent => :destroy
has_many :roles, :through => :person2roles
has_many :person2resources, :dependent => :destroy
has_many :resources, :through => :person2resources
#以下方法省略......
end
资源对象 包含两种资源 操作和模块
model/resource.rb
class Resource < ActiveRecord::Base
#2008-6-12资源属性相关-多关联
belongs_to :ph, :polymorphic => true
acts_as_tree :order => "name"
has_many :person2resources, :dependent => :destroy
has_many :people, :through => :person2resources
has_many :role2resources, :dependent => :destroy
has_many :roles, :through => :role2resources
alias_attribute :sortNo, :sortno
alias_attribute :text, :name
alias_attribute :resName, :name
alias_attribute :childRes, :children
RES_TYPE_OPERATION = "Operation"
RES_TYPE_MODULE = "Mod"
#以下方法省略......
end
角色和资源的关联的中间model
model/role2resource.rb
class Role2resource < ActiveRecord::Base
belongs_to :role
belongs_to :resource
alias_attribute :resourceId, :resource_id
#以下方法省略......
end
角色对象 和人员 资源关联
model/role.rb
class Role < ActiveRecord::Base
acts_as_tree :order => "name"
has_many :person2roles, :dependent => :destroy
has_many :people ,:through => :person2roles
has_many :role2resources, :dependent => :destroy
has_many :resources, :through => :role2resources
#下面是为了便于JSON化数据
alias_attribute :sortNo, :sortno
alias_attribute :text, :name
alias_attribute :rolename, :name
#以下方法省略......
end
下面是自己改写过的生成json的插件jsonifier
用于把对象和对象集合转换成符合EXT接受的单个对象的json格式和多个树形结构的json格式
module Jsonifier #:nodoc:
module JsonEncoding
def to_json(options = {})
hashifier = JsonHashifier.new(self, options)
hashifier.to_hash.to_json
end
class JsonHashifier #:nodoc:
attr_reader :options
def initialize(record, options = {})
@record, @options = record, options.dup
filter_attributes
end
# Outputs AR record instance method as a hash that can be easily
# encoded as JSON.
def to_hash
hash = {}
hash.merge!(simple_attributes)
hash.merge!(method_attributes)
hash.merge!(astract_attributes)
hash.merge!(checked_attributes)
hash.merge!(association_attributes)
hash.merge!(self_children_attributes)
hash.merge!(parent_object_attributes)
hash.merge!(contain_association_attributes)
hash
end
# Returns 1st level attributes as a hash.
def simple_attributes
attribute_names = @record.attribute_names
if options[:only]
options.delete(:except)
attribute_names = attribute_names & Array(options[:only]).collect(&:to_s)
else
options[:except] = Array(options[:except]) | Array(@record.class.inheritance_column)
attribute_names = attribute_names - options[:except].collect(&:to_s)
end
attribute_names.reject! { |n| binary_attribute?(n) } # Don't JSON-ify binary fields!
@record.attributes(:only => attribute_names)
end
# Returns 1st level methods as a hash.
def method_attributes
Array(options[:methods]).inject({}) do |method_attributes, name|
method_attributes.merge!({ name.to_s => @record.send(name.to_s) }) if @record.respond_to?(name.to_s)
method_attributes
end
end
# Returns 1st level methods as a hash.
#ext:=> options[:astract].is_a?(Hash)
#:astract => {:id => :resource_id}
def astract_attributes
hash = {}
if options[:astract] && options[:astract].is_a?(Hash)
options[:astract].keys.each do |key|
name = options[:astract][key]
hash.merge!({ key.to_s => @record.send(name.to_s) }) if @record.respond_to?(name.to_s)
hash
end
end
hash
end
# Returns 1st level methods as a hash.
#ext:=> options[:checked].is_a?(Hash)
#:checked => obj
#but the obj.is_a?(Hash)
#using for role_is_checked and group_is_checked
#2008-06-30
def checked_attributes
hash = {}
if options[:checked] && options[:checked].is_a?(Hash)
if options[:checked].values.include?(@record.send(:id))
hash.merge!(:checked => true)
else
hash.merge!(:checked => false)
end
hash
end
hash
end
#2008-06-13 by ytok
# Returns 1st level methods as a hash.
def filter_attributes
filter_object = options[:filter]
if filter_object && filter_object.is_a?(Hash)
filter_object.keys.each do |key|
value = filter_object[key]
if value.is_a?(Hash)
value.keys.each do |class_name|
add_method = value[class_name]
if @record.instance_of?(key.class)
if @record.ph.instance_of?(class_name.class)
options[:methods].concat(add_method)
end
end
end
end
end
end
end
# Returns 1st level associations as a hash. Recursively "hashifies"
# associations so that nth level associations are converted to JSON as well.
def association_attributes
hash = {}
if include_associations = options.delete(:include)
base_only_or_except = { :except => options[:except],
:only => options[:only] }
include_has_options = include_associations.is_a?(Hash)
for association in include_has_options ? include_associations.keys : Array(include_associations)
association_options = include_has_options ? include_associations[association] : base_only_or_except
opts = options.merge(association_options)
case @record.class.reflect_on_association(association).macro
when :has_many, :has_and_belongs_to_many
records = @record.send(association).to_a
unless records.empty?
hash[association] = records.collect { |r| JsonHashifier.new(r, opts).to_hash }
end
when :has_one, :belongs_to
if record = @record.send(association)
hash[association] = JsonHashifier.new(record, opts).to_hash
end
end
end
options[:include] = include_associations
end
hash
end
#2008-06-07 by ytok
# Returns more 1st level associations as a hash. Recursively "hashifies"
# associations so that nth level associations are converted to JSON as well.
def self_children_attributes
hash = {}
if include_associations = options[:self]
base_only_or_except = { :except => options[:except],
:only => options[:only] }
include_has_options = include_associations.is_a?(Hash)
for association in include_has_options ? include_associations.keys : Array(include_associations)
association_options = include_has_options ? include_associations[association] : base_only_or_except
opts = options.merge(association_options)
case @record.class.reflect_on_association(association).macro
when :has_many, :has_and_belongs_to_many
records = @record.send(association).to_a
unless records.empty?
hash[association] = records.collect { |r| JsonHashifier.new(r, opts).to_hash }
else
hash[association] = Array.new
end
when :has_one, :belongs_to
if record = @record.send(association)
hash[association] = JsonHashifier.new(record, opts).to_hash
end
end
end
options[:self] = include_associations
end
hash
end
#2008-06-09 by ytok
# Returns more 1st level associations as a hash. Recursively "hashifies"
# associations so that nth level associations are converted to JSON as well.
def parent_object_attributes
hash = {}
if include_associations = options[:parent_object]
base_only_or_except = { :except => options[:except],
:only => options[:only] }
include_has_options = include_associations.is_a?(Hash)
association = include_has_options ? include_associations.keys : Array.new
association.each do |associ|
association_options = include_has_options ? base_only_or_except : include_associations[associ]
opts = options.merge(association_options)
case @record.class.reflect_on_association(associ).macro
when :has_many, :has_and_belongs_to_many
records = @record.send(associ).to_a
unless records.empty?
hash[include_associations[associ]] = records.collect { |r| JsonHashifier.new(r, opts).to_hash }
else
hash[include_associations[associ]] = Array.new
end
when :has_one, :belongs_to
if record = @record.send(associ)
hash[include_associations[associ]] = JsonHashifier.new(record, opts).to_hash
else
hash[include_associations[associ]] = Array.new
end
end
end
options[:parent_object] = include_associations
end
hash
end
#2008-06-10 by ytok
# Returns more 1st level associations as a hash. Recursively "hashifies"
# associations so that nth level associations are converted to JSON as well.
def contain_association_attributes
hash = {}
if include_associations = options[:contains]
if include_associations.is_a?(Hash)
include_associations.keys.each do |contain_key|
contain_value = include_associations[contain_key]
attr_name = contain_key.to_sym
hash = component_association_transact(attr_name, contain_value)
end
else
attr_name = include_associations.to_sym
hash = component_association_transact(attr_name, nil)
end
end
hash
end
protected
def binary_attribute?(name)
!@record.class.serialized_attributes.has_key?(name) && @record.class.columns_hash[name].type == :binary
end
#2008-06-13 by ytok
#核心处理关联关系部分
def component_association_transact(attr_name, attr_value = nil)
hash = {}
include_associations = options[:contains]
base_only_or_except = { :except => options[:except],
:only => options[:only] }
association_options = base_only_or_except
#处理待过滤属性
dispose_filter_attribute
opts = options.merge(association_options)
unless attr_value
hash = dispose_association_attribute(attr_name, opts, attr_name, nil)
else
if attr_value.is_a?(Hash)
attr_value.keys.each do |associations_method|
ass_value = attr_value[associations_method]
#nothing to do
end
elsif attr_value.is_a?(Symbol)
hash = dispose_association_attribute(attr_value, opts, attr_name, nil)
else
hash = dispose_association_attribute(attr_name, opts, attr_name, attr_value)
end
end
options[:contains] = include_associations
hash
end
#2008-06-13 by ytok
#处理关联关系核心
def dispose_association_attribute(association, opts, association_value, astract_value = nil)
hash = {}
case @record.class.reflect_on_association(association).macro
when :has_many, :has_and_belongs_to_many
records = @record.send(association).to_a
unless records.empty?
hash[association_value] = records.collect { |r|
unless astract_value && r.ph.instance_of?(astract_value.class)
JsonHashifier.new(r, opts).to_hash
end
}
#处理掉空元素
temp = Array.new
hash[association_value].each do |item|
unless !item
temp << item
end
end
hash[association_value] = temp
else
hash[association_value] = Array.new
end
when :has_one, :belongs_to
if record = @record.send(association)
unless astract_value && record.ph.instance_of?(astract_value.class)
hash[association_value] = JsonHashifier.new(record, opts).to_hash
else
hash[association_value] = Array.new
end
else
hash[association_value] = Array.new
end
end
hash
end
#处理待过滤属性
def dispose_filter_attribute
filter_attr_name = Array.new
filter_object = options[:filter]
if filter_object && filter_object.is_a?(Hash)
filter_object.keys.each do |key|
filter_object[key].values.each do |value|
value.each do |item|
filter_attr_name << item
end
end
end
end
filter_attr_name.each do |item|
options[:methods].delete(item)
end
end
end
end
end
ActiveRecord::Base.send(:include, Jsonifier::JsonEncoding)
ps======================================== 下面后续的说明其整个项目组织结构和开发中遇到的问题