Rails:使用validated属性创建值对象

时间:2013-10-04 04:58:15

标签: ruby-on-rails ruby ruby-on-rails-3

我有两个模型都具有相同的状态(草稿,实时,非活动)。我喜欢通过值对象来干掉我的代码的想法。我创建了以下代码:

class CurrentStatus
  STATUSES = %w"DRAFT LIVE INACTIVE"
  attr_reader :status

  def initialize(status)
    stringed_status = status.to_s
    if STATUSES.include?(stringed_status)
      @status = stringed_status
    else
      raise "Invalid state for object Status"
    end
  end
end

在模型中:

class Interest < ActiveRecord::Base
  composed_of :status, :class_name => 'CurrentStatus', :mapping => %w(status)
  attr_accessible :description, :name, :status

这使我能够成功执行:

[47] pry(main)> i = Interest.new
=> #<Interest id: nil, name: nil, description: nil, created_at: nil, updated_at: nil, status: nil>
[49] pry(main)> i.status = CurrentStatus.new('blech')
RuntimeError: Invalid state for object Status from /app/models/current_status.rb:10:in `initialize'
[50] pry(main)> i.status = CurrentStatus.new('DRAFT')
=> DRAFT
[51] pry(main)> i
=> #<Interest id: nil, name: nil, description: nil, created_at: nil, updated_at: nil, status: "DRAFT">

但不是:

[48] pry(main)> i.status = 'DRAFT'
NoMethodError: undefined method `status' for "DRAFT":String
from ruby-1.9.3-p429/gems/activerecord- 3.2.13/lib/active_record/aggregations.rb:248:in `block (2 levels) in writer_method'

所以在InterestsController中我调用新方法:

  def new
    @interest = Interest.new

并拉出表格:

    <div class="field">
      <%= f.label :status %><br />
      <%= f.select(:status, %w"DRAFT REPORT_ONLY LIVE SUSPENDED" ) %>

我的验证阻止了我:

  Rendered interests/_form.html.erb (70.1ms)
  Rendered interests/new.html.erb within layouts/application (83.0ms)
Completed 500 Internal Server Error in 465ms

RuntimeError - Invalid state for object Status:
  app/models/current_status.rb:10:in `initialize'
  activerecord (3.2.13) lib/active_record/aggregations.rb:229:in `block in reader_method'

对我来说,编写此验证的更好方法是什么?

2 个答案:

答案 0 :(得分:1)

感谢您帮助materialdesigner。我也把它发给了一些朋友。你的答案让我和他们一样在那里。

他们说服我摆脱'composed_of',因为这个词可能会被弃用。我接受了你的建议并使用了ActiveModel :: Validations。这是最终的工作代码:

这是CurrentStatus:

class CurrentStatus
  include ActiveModel::Validations
  STATUSES = %w"DRAFT LIVE SUSPENDED"
  attr_reader :status

  validates_inclusion_of :status, :in => STATUSES

  def initialize(status)
    @status = status.to_s
  end

  def self.valid_statuses
    STATUSES.to_a
  end

  def self.to_a
    self.valid_statuses
  end

  def to_s
    @status.to_s
  end
end

和Interests.rb

# == Schema Information
#
# Table name: interests
#
#  id          :integer          not null, primary key
#  name        :string(255)
#  description :text
#  created_at  :datetime         not null
#  updated_at  :datetime         not null
#  status_key  :string(255)
#

class Interest < ActiveRecord::Base
  attr_accessible :description, :name, :status_key, :status

  validate :valid_status

  def status
    return CurrentStatus.new(status_key)
  end

  def status=(value)
    self.status_key = value
  end

  private
  def valid_status
    errors.add(:status_key, "is invalid") unless status.valid?
  end

end

此代码允许我说Interests.create(status: 'INVALID_STATUS'),系统会在允许通过Interests.create(status: 'LIVE')时阻止保存。谢谢你的帮助!

答案 1 :(得分:0)

我认为使用ActiveModel验证更有意义 当整个Interest模型试图被保存时,应该触发这些。

class CurrentStatus
  include ActiveModel::Validations

  STATUSES = %w(DRAFT LIVE INACTIVE)
  attr_reader :status

  validates_inclusion_of :status, :in => STATUSES

  def initialize(status)
    stringed_status = status.to_s
    @status = status.to_s
  end
end