Раздел «Язык Ruby».RubySchoolSourceL12:
<<Программирование на Ruby

Занятие 12: Валидация виртуальных аттрибутов

Следующее занятие
Предыдущее занятие

Код для создания приложения

rails new blog
cd blog
./script/rails generate scaffold user nickname:string role:integer status:integer email:string
./script/rails generate scaffold network title:string creator_id:integer status:integer options:test
./script/rails generate scaffold member user_id:integer network_id:integer role:integer
./script/rails generate scaffold item  network_id:integer author_id:integer status:integer category_id:integer title:string 
./script/rails generate scaffold category network_id:integer status:integer title:string

# app/models/member.rb
class Member < ActiveRecord::Base
  default_scope :conditions => { :status => 0 }
  belongs_to :network
  belongs_to :user
end

# app/models/network.rb
class Network < ActiveRecord::Base
  default_scope :conditions => { :status => 0 }
  has_many :members
  has_many :items
  has_many :categories
end

# app/models/user.rb
class User < ActiveRecord::Base
  default_scope :conditions => { :status => 0 }
  has_many :items, :foreign_key => 'author_id'
  has_many :members
  has_many :networks, :through => 'members'
  validates_existence_of :email
  validates_existence_of :nickname
  validates_length_of :nickname, :in => (3..40)
end

class Category
  belongs_to :network
  validates_length_of :title, :in => (3..100)
end

Домашнее задание

сlass Item < ActiveRecord::Base
  default_scope :conditions => { :status => 0 }

  belongs_to  :author, :class_name => "User", :foreign_key => 'author_id'
  belongs_to  :network
  belongs_to  :category
  has_many    :comments, :order => 'created_at', :dependent => :destroy

#  attr_accessor :custom_errors

#  def custom_errors
#    @custom_erros ||= []
#  end

  def network_title
#   @network_title ||= (
      self.network(true) && self.network.title
#   )
  end

  def network_title=(t)
#   self.custom_erros.delete_if {|atrr_name,str| attr_name == :network_id}
    if t.blank?
      self.network_id = nil
    else
      if n = Network.find_by_title(t)
        self.network_id = n.id
        self.network(true)
        @network_title = t
      else
        self.custom_errors << [:network_id, "указанного сообщества не существует"]
      end
    end
  end

  # Хотелось бы, чтобы данная строчка работала как закоментированный код
  # (выше и ниже)
  
  virtual_attribute :network_title, :category_title
  
#  validate do |item|
#    item.custom_errors.each do |attr_name, str|
#      item.errors.add attr_name, str
#    end
#  end
end

Нужно написать примесь VirtualAttributes?, которую примешать в ActiveRecord?::Base

# lib/virtual_attribute.rb
module VirtualAttribute
  # TODO
end

# config/initializers/001_active_record_ext.rb
ActiveRecord::Base.class_eval do
  include VirtualAttribute
end

После чего должен работать следующий код:

сlass Item < ActiveRecord::Base
  default_scope :conditions => { :status => 0 }

  belongs_to :author, :class_name => "User", :foreign_key => 'author_id'
  belongs_to :network
  belongs_to  :category
  has_many    :comments, :order => 'created_at', :dependent => :destroy
  validates_length_of :title, :in => (3..1024)

  def network_title
    self.network(true) && self.network.title
  end

  def network_title=(t)
    if t.blank?
      self.network_id = nil
    else
      if n = Network.find_by_title(t)
        self.network_id = n.id
        self.network(true)
      else
        self.custom_errors << [:network_id, "указанного сообщества не существует"]
      end
    end
  end

  virtual_attribute :network_title
end

Вызов virtual_attribute

  virtual_attribute :network_title
должен патчить методы :network_title и :network_title= так, что

Например, в консоли должен работать следующий код:

> n = Network.create(:title => "Существующее сообщество", :status => 0)
> i = Item.create(:network_id => n.id, :title => "test", :status => 0)
> i = Item.find(i.id); i.network_title = "Несуществующее сообщество"; i.save
=> false
> i.instance_eval do { @network_title }
=> "Несуществующее сообщество"
>  i.network_title = "Существующее сообщество"; i.save
=> true
> i.instance_eval do {@network_title}
=> "Несуществующее сообщество"
> i.reload
> i.instance_eval do {@network_title}
=> nil
> i.network_title
=> "Существующее сообщество"
> i.instance_eval do {@network_title}
=> "Существующее сообщество"

-- ArtemVoroztsov - 09 Apr 2011