rails笔记 activerecord 2
扩展的activerecord
act_as_list
act_as_list : one-to-many对象关系是通过list(默认是set)来完成, 既然有list, 就有了排序,首先对应表中必须有position(自动默认)字段,用来标示排序,如果不默认就得显示通过:order指定
class Parent < ActiveRecord::Base
has_many :children, :order => :position
end
class Child < ActiveRecord::Base
belongs_to :parent
acts_as_list :scope => :parent_id
end
这里的:scope=> :parent_id 说明了list是相对于单个parent_id的, 否则就是所有的product共用一个排序
对应关系建立以后, child对象会增加如下move_lower,move_higher, move_to_top,move_to_bottom,还会有first?和last?方法
注意 对对象作了排序操作以后, 只是修改数据库中的记录, parent对象还不会在list中调整, 必须调用parent.reload
act_as_tree
和act_as_list类似,例子如下
class Category < ActiveRecord::Base
acts_as_tree :order=> "name"
end
实际上这个代码等同于===>
class Category < ActiveRecord::Base
belongs_to :parent,
:class_name => "Category"
has_many :children,
:class_name => "Category",
:foreign_key => "parent_id",
:order => "name",
:dependent => true
end
通过children操作, 如有必要,可以通过:counter_cache=> true 并添加children_count来优化子对象数量获取
aggregation hibernate的 component
简单的说就是把几个字段映射为一个字段,方法为新建立一个对象 class Name attr_reader :first,:last end 然后在主对象中使用composed_of
class XX < ActiveRecord::Base
composed_of :name, :class_name=>Name,:mapping=>
[
[:first_name,:first]
[:last_name,:last]
]
end
其中:class_name和mapping可省略 ???
aggregation还可以用来聚合单一字段,下面的类就聚合了一个字段(以,号分隔)
class XxYy
attr_reader :list
def initialize(db_str)
@list=db.str.split(/,/)
end
def xx_yy
@list.join(',')
end
end
然后主类需要用composed_of 标明映射的字段xx_yy
class Main
composed_of :xx_yy
end
注意 aggregation对象都是value object, 你不能修改他里面的值,即使修改了,rails也不会回写它们到数据库中,唯一修改的办法是新建另外一个component然后赋给主对象
单表继承
只适用于大部分值都在父类的对象结构, 对于abstact方法的对象结构(子类直接差别非常大),可能不适合
- 表中必须有所有子对象的属性, 不能有重合,冲突,所有 父类可能有很多允许NULL的字段
- 表中有个type字段标示类型 ,根对象的type是"",
- 子对象的类型实际上父对象也有,所有这里的对象不严格了, rail说是权宜之计
- 由于type对象和ruby内置的type属性冲突,所有要用model[:type]来访问
验证 validation模块
底层有三个方法
- validate 任何保存操作都调用
- validate_on_create 创建对象的时候调用 通过new_record?区分
- validate_on_update 更新对象的时候调用
实现这三个方法就可以自动使用validate, 手动调用的方法是valid?()
validate就是简单的判断, 然后再errors.add(:xxxx,"readon")就可以,例子如下
class User < ActiveRecord::Base
def validate
unless name && name =~ /^\w+$/
errors.add(:name, "is missing or invalid")
end
end
def validate_on_create
if self.find_by_name(name)
errors.add(:name, "is already being used")
end
end
end
提示 rails增加了一个blank?方法 可以判断string为nil或""
高层的帮助指令(很多)
大部分指令都支持:on和:message参数,
- :on标示何时进行验证
- :message标示验证失败的帮助信息(可格式化)
失败以后,controller会自动重新显示form,然后page可以通过调用error_messages_for()来显示错误消息
指令列表如下
- validates_acceptance_of 用于检查checkbox, 检查对应参数的值是否为"1"
- validates_asscociated 调用指派model自己的validate, 此方法指派的属性必须也是一个ActiveRecord的Model, 这个方法需要注意不要形成循环验证
- validates_confirmation_of 用来检查如同password的带confirm的输入,同时检查xxx字段和xxx_confirmation是否一样(约定)
- validates_each {|model,attr,value| ...} 一次检查多个字段,需要传入一个block, :allow_nil参数如果为true(默认false), nil就不会被传入进来
- validates_exclusion_of attr....,:in=> enum[] 验证对应的参数不在给的列表内(支持include?的都行)
- validates_inclusion_of 和上门相反
- validates_format_of :with=>/..../ 正则匹配
- validates_length_of 验证长度 有:maximum :minimum :in可用, :message还有三个扩展:too_long :too_short :wrong_length对应细化的错误信息(格式化支持%d标识要求的数值)
- validates_numericality_of 验证数字, 支持:only_integer(支持负数)
- validates_presence_of 验证非空
- validates_uniqueness_of 验证唯一 :scope=>"xx" 指定的字段可以作为范围(在同一个xx中唯一)
Callbacks
一共16个callback, 其中14个如下图调用 (可以看出没有严格嵌套)
另外两个特殊的callback是after_find after_initialize
callback的注册可以通过
- 直接在类中写方法,如def before_create end
- 通过类指令: before_cate :some_method
- 通过提供一个block {|model|...}
- 提供一个callback对象的实例,单独的Callback对象相当于专门把callback方法独立出来放入一个类中,使得callback可以在类之间复用(默认在/app/model下)
要统一注册callback ,可以修改ActiveRecord::Base根类
注意 字段created_at created_on updated_at updated_on会自动被rails更新
Observers 类似aop
默认在app/model下一个典型的例子, 如果不使用observe类指令, 默认是根据类名推导出来为Audit类
class AuditObserver < ActiveRecord::Observer observe Order, Payment, Refund def after_save(model) model.logger.info("#{model.class.name} #{model.id} created") end end
最后要调用一下 XXXObserver.instance方法才能生效, 在rails中可以在controller中使用observe方法,不用单独instance了
属性扩展 Advanced Attributes
model.attributes可以取得属性hash,可以访问特殊名的字段
特殊的组合sql可能无法得到字段类型(mysql5.0好像没有这个问题了),导致rails以文本方式保存数据,这时我们可以通过调用read_attribute("xxx")和write_attribute("xxx")可以直接在底层操作数据,作一些转换来构造一个facade column来满足需求,例子如下
class ProductData < ActiveRecord::Base
CUBITS_TO_INCHES = 18
def length
read_attribute("length") * CUBITS_TO_INCHES
end
def length=(inches)
write_attribute("length", Float(inches) / CUBITS_TO_INCHES)
end
end
注意事项
- 为万无一失, 使用find_by_sql最好都把id给select出来, 不然不能保存
- rails复写了ruby的id和hash功能,id是数据库ID(原来是object_id),如果id相等则认为对象相等,所有没有保存的对象(没有id)会都相等,用来hash不安全
- 使用原始的connection: Order.connection.select_all(..) ,详情查阅文档
magic column name
- created_at, created_on, updated_at, updated_on Automatically updated with the timestamp (_at form) or date (_on form) of a row’s creation or last update (page 267).
- lock_version Rails will track row version numbers and perform optimistic locking if a table contains lock_version (page 213).
- type Used by single table inheritance to track the type of a row (page 253).
- id Default name of a table’s primary key column (page 197).
- xxx_id Default name of a foreign key reference to table named with the singlua form of xxx (page 216).
- xxx_count Maintains a counter cache for the child table xxx (page 235).
- position The position of this row in a list if acts_as_list is used (page 243).
- parent_id A reference to the id of this row’s parent if acts_as_tree is used (page