跳过正文

第7篇:ActiveModel 接口

·499 字·3 分钟
xieweicong
作者
xieweicong

学习目标:理解 ActiveModel 如何为非 ActiveRecord 对象提供模型特性

引言
#

ActiveModel 提供了一组模块,让任何 Ruby 对象都能表现得像 ActiveRecord 模型。

1. ActiveModel::Model
#

1.1 基本用法
#

class Person
  include ActiveModel::Model

  attr_accessor :name, :age

  validates :name, presence: true
  validates :age, numericality: true
end

person = Person.new(name: "John", age: 30)
person.valid?  # => true
person.errors  # => #<ActiveModel::Errors>

2. 核心模块
#

2.1 Validations - 验证
#

class User
  include ActiveModel::Validations

  attr_accessor :email, :password

  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :password, length: { minimum: 6 }
  validate :custom_validation

  def custom_validation
    errors.add(:email, "is taken") if email == "taken@example.com"
  end
end

2.2 Callbacks - 回调
#

class Person
  extend ActiveModel::Callbacks

  define_model_callbacks :save

  before_save :normalize_name
  after_save :send_notification

  def save
    run_callbacks :save do
      # 保存逻辑
    end
  end
end

2.3 Dirty Tracking - 脏检查
#

class Person
  include ActiveModel::Dirty

  define_attribute_methods :name

  attr_reader :name

  def name=(value)
    name_will_change!
    @name = value
  end
end

person = Person.new
person.name = "John"
person.changed?       # => true
person.name_changed?  # => true
person.name_was       # => nil
person.name_change    # => [nil, "John"]

2.4 Attributes - 属性系统
#

class Person
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :age, :integer, default: 18
  attribute :born_at, :datetime
end

person = Person.new
person.age  # => 18

3. 序列化
#

3.1 Serialization
#

class Person
  include ActiveModel::Serialization

  attr_accessor :name, :age

  def attributes
    { 'name' => name, 'age' => age }
  end
end

person = Person.new
person.name = "John"
person.serializable_hash
# => {"name" => "John", "age" => nil}

3.2 JSON 序列化
#

class Person
  include ActiveModel::Serializers::JSON

  attr_accessor :name, :age

  def attributes
    { 'name' => nil, 'age' => nil }
  end
end

person = Person.new
person.name = "John"
person.age = 30
person.to_json
# => "{\"name\":\"John\",\"age\":30}"

4. 命名和转换
#

4.1 Naming
#

class Person
  extend ActiveModel::Naming
end

Person.model_name.name          # => "Person"
Person.model_name.singular      # => "person"
Person.model_name.plural        # => "people"
Person.model_name.route_key     # => "people"
Person.model_name.param_key     # => "person"

4.2 Conversion
#

class Person
  include ActiveModel::Conversion

  attr_accessor :id

  def persisted?
    id.present?
  end
end

person = Person.new
person.to_model    # => person
person.to_key      # => nil
person.to_param    # => nil

person.id = 1
person.to_key      # => [1]
person.to_param    # => "1"

5. 表单对象模式
#

5.1 实战示例
#

class UserRegistration
  include ActiveModel::Model

  attr_accessor :email, :password, :password_confirmation, :accept_terms

  validates :email, presence: true, email: true
  validates :password, presence: true, length: { minimum: 6 }
  validates :password_confirmation, presence: true
  validates :accept_terms, acceptance: true
  validate :passwords_match

  def save
    return false unless valid?

    User.transaction do
      user = User.create!(email: email, password: password)
      UserMailer.welcome(user).deliver_later
      true
    end
  rescue ActiveRecord::RecordInvalid
    false
  end

  private
    def passwords_match
      return if password == password_confirmation
      errors.add(:password_confirmation, "doesn't match password")
    end
end

# 在控制器中使用
def create
  @registration = UserRegistration.new(registration_params)
  if @registration.save
    redirect_to root_path, notice: "Welcome!"
  else
    render :new
  end
end

6. 自定义验证器
#

6.1 验证器类
#

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors.add(attribute, options[:message] || "is not a valid email")
    end
  end
end

class User
  include ActiveModel::Validations

  attr_accessor :email

  validates :email, email: true
end

7. 本章总结
#

ActiveModel 提供了丰富的模块,让你能够:

  • 为任何 Ruby 对象添加验证
  • 实现表单对象模式
  • 支持序列化和转换
  • 使用回调和脏检查

8. 练习题
#

  1. 创建一个表单对象处理复杂的注册流程
  2. 实现一个自定义验证器
  3. 使用 ActiveModel 创建一个 PORO(Plain Old Ruby Object)模型

上一篇ActiveRecord 关联关系 下一篇ActionPack 概览 返回学习指南首页