fangtingting
12/17/2017 - 8:15 AM

Active Model代码示例

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