Active Model代码示例
# rails generate model User
class User < ActiveRecord::Base
# 不用默认的命名约定
self.table="USER"
# 指定数据表的主键
self.primary_key="user_id"
# 别名:新名称充当第一个参数,原有名称是第二个参数
alias_attribute :login, :email
# Active Record 验证结束后,所有发现的错误都可以通过实例方法 obj.errors.full_messages 获取或者obj.errors[:attribute],该方法返回一个错误集合。
# 如果数据验证后,这个集合为空,则说明对象是合法的。使用obj.errors.clear清除错误消息,obj.errors.size获取错误个数。
# on、message、allow_nil、allow_blank是所有验证方式都有的选项。
# 检查指定的属性是否为非空值,调用 blank? 方法检查值是否为 nil 或空字符串,即空字符串或只包含空白的字符串。
validates :name, :login, :email, presence: true
# 验证指定的属性值是否为空,使用 present? 方法检测值是否为 nil 或空字符串,即空字符串或只包含空白的字符串。
validates :name, :login, :email, absence: true
# 验证属性值是否是唯一的,case_sensitive 选项,指定唯一性验证是否要区分大小写,默认值为 true
validates :email, uniqueness: true
validates :name, uniqueness: { case_sensitive: false }
# 验证关联的模型对象。保存对象时,会在相关联的每个对象上调用 valid? 方法。不要在关联的两端都使用 validates_associated,这样会生成一个循环。
# 注意,相关联的每个对象都有各自的 errors 集合,错误消息不会都集中在调用该方法的模型对象上。
validates_associated :books
# 检查两个文本字段的值是否完全相同,这个帮助方法会创建一个虚拟属性,其名字为要验证的属性名后加 _confirmation
validates :password, confirmation: true
validates :password_confirmation, presence: true
# 检查属性的值是否不在指定的集合中。集合可以是任何一种可枚举的对象
validates :subdomain, exclusion: { in: %w(www us ca jp),message: "%{value} is reserved." }
# 检查属性的值是否在指定的集合中。集合可以是任何一种可枚举的对象。
validates :size, inclusion: { in: %w(small medium large),message: "%{value} is not a valid size" }
# 检查属性的值是否匹配 :with 选项指定的正则表达式
validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,message: "only allows letters" }
# 验证属性值的长度,有多个选项,可以使用不同的方法指定长度限制
validates :content, length: {
minimum: 300,
maximum: 400,
tokenizer: lambda { |str| str.scan(/\w+/) },
too_short: "must have at least %{count} words",
too_long: "must have at most %{count} words",
wrong_length: ""
}
validates :bio, length: { maximum: 500 ,too_long: "%{count} characters is the maximum allowed" },on: :create
validates :password, length: { in: 6..20 }, allow_blank: true
validates :registration_number, length: { is: 6 }, allow_nil: true
# 把记录交给其他的类做验证
validates_with GoodnessValidator,fields: [:first_name, :last_name]
# 条件验证
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
validates :password, confirmation: true,unless: Proc.new { |a| a.password.blank? }
# 自定义验证
validate :active_customer, on: :create
def active_customer
# 添加错误信息
errors.add(:customer_id, "is not active") unless customer.active?
end
# 关联回调
has_many :posts, dependent: :destroy
# 作用域:所有作用域方法都会返回一个 ActiveRecord::Relation 对象,允许继续调用其他方法
scope :published, -> { where(published: true) }
scope :published_and_commented, -> { published.where("comments_count > 0") }
scope :created_before, ->(time) { where("created_at < ?", time) }
# 默认作用域,在其他条件前自动调用
default_scope :order => "created_at desc"
default_scope { where("removed_at IS NULL") }
# 删除所有作用域
Client.unscoped.load
end
class GoodnessValidator < ActiveModel::Validator
def validate(record)
if options[:fields].any?{|field| record.send(field) == "Evil" }
# 错误消息可以添加到整个对象上,而不是针对某个属性
record.errors[:base] << "This person is evil"
end
end
end
# create、create!、save、save!、update、update!方法会验证数据库
# 爆炸方法(例如 save!)会在验证失败后抛出异常。验证失败后,非爆炸方法不会抛出异常,save 和 update 返回 false,create 返回对象本身。
# 以下方法会调过验证
# decrement!、decrement_counter、increment!、increment_counter、toggle!、touch、update_all
# update_attribute、update_column、update_columns、update_counters
# 使用 save 时如果传入 validate: false,也会跳过验证。使用时要特别留意。
# valid? 方法会触发数据验证,如果对象上没有错误,就返回 true,否则返回 false。
User.create(name: "John Doe").valid? # => true
User.create(name: nil).valid? # => false
# errors[:attribute]检查对象的某个属性是否合法,如果某个属性没有错误,就会返回空数组。
User.new.errors[:name].any? # => false
User.create.errors[:name].any? # => true
# 创建对象回调
# before_validation、after_validation、before_save、around_save、before_create、around_create、after_create、after_save
# 更新对象回调
# before_validation、after_validation、before_save、around_save、before_update、around_update、after_update、after_save
# 销毁对象时回调
# before_destroy、around_destroy、after_destroy
# after_initialize 回调在新对象初始化时触发执行,after_find 回调由查询方法触发执行
# 事务回调
# after_commit 和 after_rollback
# 触发回调
# create、create!、decrement!、destroy、destroy!、destroy_all、increment!、save、save!
# save(validate: false)、toggle!、update_attribute、update、update!、valid?
# 调过回调
# decrement、decrement_counter、delete、delete_all、increment、increment_counter、toggle、touch
# update_column、update_columns、update_all、update_counters
# 关联
# belongs_to、has_one、has_many、has_many :through、has_one :through、has_and_belongs_to_many
# 使用 belongs_to 还是 has_one:如果想建立两个模型之间的一对一关系,可以在一个模型中声明 belongs_to,然后在另一模型中声明 has_one。
# 但是怎么知道在哪个模型中声明哪种关联?不同的声明方式带来的区别是外键放在哪个模型对应的数据表中
# 使用 has_many :through 还是 has_and_belongs_to_many:其中比较简单的是 has_and_belongs_to_many,可以直接建立关联。
# 使用 has_many :through,但无法直接建立关联,要通过第三个模型
# 多态关联:在同一个关联中,模型可以属于其他多个模型
class Picture < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
end
class Employee < ActiveRecord::Base
has_many :pictures, as: :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, as: :imageable
end
# 可以使用 @employee.pictures 获取图片集合。类似地,可使用 @product.pictures 获取产品的图片。
# 在 Picture 模型的实例上,可以使用 @picture.imageable 获取父对象。不过事先要在声明多态接口的模型中创建外键字段和类型字段:
class CreatePictures < ActiveRecord::Migration
def change
create_table :pictures do |t|
t.string :name
t.integer :imageable_id
t.string :imageable_type
# t.references :imageable, polymorphic: true 代替imageable_id跟imageable_type
t.timestamps
end
end
end
# 自连接:数据是树形结构
class Employee < ActiveRecord::Base
has_many :subordinates, class_name: "Employee",
foreign_key: "manager_id"
# 不同命名空间下关联
has_many :accounts,class_name: "MyApplication::Account"
belongs_to :manager, class_name: "Employee"
end
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.references :manager
t.timestamps
end
end
end
# 双向关联,保持数据一致性。一般都不太用这个属性。inverse_of 有些限制:
# 不能和 :through 选项同时使用;
# 不能和 :polymorphic 选项同时使用;
# 不能和 :as 选项同时使用;
# 在 belongs_to 关联中,会忽略 has_many 关联的 inverse_of 选项;
class Customer < ActiveRecord::Base
has_many :orders, inverse_of: :customer
# has_many选项
has_many :orders, dependent: :delete_all, validate: :false
end
class Order < ActiveRecord::Base
belongs_to :customer, inverse_of: :orders
# 使用的字段要添加到关联的模型中(counter_cache: true默认是(table_name)_count)
belongs_to :customer,counter_cache: :count_of_orders
# belongs_to作用域
belongs_to :customer, -> { where active: true }, dependent: :destroy
has_one :user,through: :product,source: "PRODUCT"
# 编写复杂表单:accepts_nested_attributes_for用法, 如果属性组成的 Hash 中包含 _destroy 键,且其值为 1 或 true,就会删除对象
accepts_nested_attributes_for :addresses, allow_destroy: true, reject_if: lambda {|attributes| attributes['kind'].blank?}
end
# 声明 belongs_to 关联后,所在的类自动获得了五个和关联相关的方法(association用关联名代替):
# association(force_reload = false)、association=(associate)、build_association(attributes = {})
# create_association(attributes = {})、create_association!(attributes = {})
# 声明 has_one 关联后,声明所在的类自动获得了五个关联相关的方法(association用关联名代替):
# association(force_reload = false)、association=(associate)、build_association(attributes = {})
# create_association(attributes = {})、create_association!(attributes = {})
# 声明 has_many 关联后,声明所在的类自动获得了 16 个关联相关的方法(collection 要替换成传入 has_many 方法的第一个参数):
# collection(force_reload = false)、collection<<(object, ...)、collection.delete(object, ...)
# collection.destroy(object, ...)、collection=objects、collection_singular_ids
# collection_singular_ids=ids、collection.clear、collection.empty?、collection.size
# collection.find(...)、collection.where(...)、collection.exists?(...)、collection.build(attributes = {}, ...)
# collection.create(attributes = {})、collection.create!(attributes = {})
# 声明 has_and_belongs_to_many 关联后,声明所在的类自动获得了 16 个关联相关的方法(collection 要替换成传入 has_and_belongs_to_many 方法的第一个参数):
# collection(force_reload = false)、collection<<(object, ...)、collection.delete(object, ...)
# collection.destroy(object, ...)、collection=objects、collection_singular_ids
# collection_singular_ids=ids、collection.clear、collection.empty?
# collection.size、collection.find(...)、collection.where(...)、collection.exists?(...)
# collection.build(attributes = {})、collection.create(attributes = {})、collection.create!(attributes = {})
class AccountHistory < ActiveRecord::Base
belongs_to :account
#belong_to参数选项
autosave: true , #保存父类,自动保存所有子类,并把标记为析构的子对象销毁。
class_name: "Parton" , #指定类名
counter_cache: true , #计数缓存功能, Rails 会及时更新缓存,调用@customer.orders.size方法时返回缓存中的值。虽然 :counter_cache 选项在声明 belongs_to 关联的模型中设置,但实际使用的字段要添加到关联的模型中。针对上面的例子,要把 orders_count 字段加入 Customer 模型。counter_cache: :count_of_orders设置字段的默认名。
dependent: :destroy ,#在关联对象上调用 destroy 方法
foreign_key: "parton_id", #设置使用的外键名
inverse_of: :orders, #选项指定 belongs_to 关联另一端的 has_many 和 has_one 关联名。Active Record 并不知道这个关联中两个模型之间的联系,通过inverse_of实现双向关联。Active Record 提供了 :inverse_of 选项,可以告知 Rails 两者之间的关系。inverse_of限制:不能和 :through、:polymorphic、:foreign_key、:as、:conditions 选项同时使用;在 belongs_to 关联中,会忽略 has_many 关联的 inverse_of 选项。
touch: true #保存或销毁对象时,关联对象的 updated_at 或 updated_on 字段会自动设为当前时间戳。touch: :orders_updated_at指定更新字段。
validate: true #保存对象时,会同时验证关联对象。该选项的默认值是 false,保存对象时不验证关联对象。
end
class Account < ActiveRecord::Base
belongs_to :company
has_one :account_history
end
class Company < ActiveRecord::Base
has_one :account
#通过第三方模型建立一对一关联,要创建连接类和数据库连接表
has_one :account_history, through: :account,
#has_one选项
autosave: true, #保存父类,自动保存所有子类,并把标记为析构的子对象销毁。
class_name: "Parton" , #指定类名
dependent: :destroy #在关联对象上调用 destroy 方法
foreign_key: "parton_id", #设置使用的外键名
primary_key: "account_id", #设置关联对象主键名
source: "" , #指定 has_one :through 关联的关联源名字
source_type: "" , #指定 has_one :through 关联中用来处理多态关联的关联源类型
validate: true , #保存对象时,会同时验证关联对象。该选项的默认值是 false,保存对象时不验证关联对象。
inverse_of: :company
has_many :orders ,
#has_many选项
as: "" , #多态关联
autosve: true,
class_name: "Parton",
dependent: :destroy , #销毁所有关联对象。
foreign_key: "parton_id",
primary_key: "account_id",
inverse_of: :customer,
source: "" ,#指定 has_many :through 关联的关联源名字
source_type: "" , #指定 has_many :through 关联中用来处理多态关联的关联源类型
validate: true
# 多对多关联,都需要第三方连接表来连接。has_many :through需要定义第三方连接类;has_and_belongs_to_many不需要定义第三方连接类,第三方连接表名按照类名出现在字典中的顺序为数据表起名字。
has_and_belongs_to_many :assemblies,
#has_and_belongs_to_many选项
association_foreign_key: "this_assem_id" #设置关联对象在第三方数据连接表外键名
autosve: true
class_name: "Ass"
foreign_key: "this_id" #设置模型在第三方数据连接表外键名
join_table: "Gadget" #如果默认按照字典顺序生成的默认名不能满足要求,可以使用 :join_table 选项指定。
validate: true
readonly: true
end
# 关联回调
class Customer < ActiveRecord::Base
has_many :orders, before_add: :check_credit_limit
def check_credit_limit(order)
...
end
# 关联扩展:在扩展中可以使用如下 proxy_association 方法的三个属性获取关联代理的内部信息:
# proxy_association.owner:返回关联所属的对象;
# proxy_association.reflection:返回描述关联的反射对象;
# proxy_association.target:返回 belongs_to 或 has_one 关联的关联对象,或者 has_many 或 has_and_belongs_to_many 关联的关联对象集合;
has_many :orders do
def find_by_order_prefix(order_number)
find_by(region_id: order_number[0..2])
end
end
has_many :orders, -> { extending FindRecentExtension }
end
module FindRecentExtension
def find_recent
where("created_at > ?", 5.days.ago)
end
end
@order = @customer.orders.create(order_date: Time.now)
@order = @customer.orders.find(params[:id])
# 更新时锁定记录
# 1、乐观锁定允许多个用户编辑同一个记录,假设数据发生冲突的可能性最小。Rails 会检查读取记录后是否有其他程序在修改这个记录。如果检测到有其他程序在修改,就会抛出 ActiveRecord::StaleObjectError 异常,忽略改动。
# 为了使用乐观锁定,数据表中要有一个类型为整数的 lock_version 字段。每次更新记录时,Active Record 都会增加 lock_version 字段的值。如果更新请求中的 lock_version 字段值比数据库中的 lock_version 字段值小,会抛出 ActiveRecord::StaleObjectError 异常,更新失败
# 要想修改 lock_version 字段的名字,可以使用 ActiveRecord::Base 提供的 locking_column 类
c1 = Client.find(1)
c2 = Client.find(1)
c1.first_name = "Michael"
c1.save
c2.name = "should fail"
c2.save # Raises an ActiveRecord::StaleObjectError
class Client < ActiveRecord::Base
self.locking_column = :lock_client_column
end
# 2、悲观锁定使用底层数据库提供的锁定机制。使用 lock 方法构建的关系在所选记录上生成一个“互斥锁”(exclusive lock)。使用 lock 方法构建的关系一般都放入事务中,避免死锁。
Item.transaction do
i = Item.lock.first
i.name = 'Jones'
i.save
end