ActiveRecord Проблемы с использованием обратных вызовов и STI - PullRequest
2 голосов
/ 23 декабря 2010

Привет, ребята, следующая проблема с Rails и STI:

У меня есть следующие классы:

class Account < AC::Base
  has_many :users
end

class User < AC::Base
  extend STI
  belongs_to :account

  class Standard < User
    before_save :some_callback
  end

  class Other < User
  end
end

module STI
  def new(*args, &block)
    type = args.dup.extract_options!.with_indifferent_access.delete(:type)
    if type.blank? or (type = type.constantize) == self
      super(*args, &block)
    else
      type.new(*args, &block)
    end
  end
end

А теперь проблема: без переписывания User.new (в модуле STI),обратный вызов внутри User::Standard никогда не вызывается, в противном случае account_id всегда будет nil, если я создаю пользователей таким образом:

account.users.create([{ :type => 'User::Standard', :firstname => ... }, { :type => 'User::Other', :firstname => ... }])

Если я использую другой подход для модуля, например:

module STI
  def new(*args, &block)
    type = args.dup.extract_options!.with_indifferent_access.delete(:type)
    if type.blank? or (type = type.constantize) == self
      super(*args, &block)
    else
      super(*args, &block).becomes(type)
    end
  end
end

Тогда переменные экземпляра не являются общими, поскольку они создают новый объект.Есть ли какое-либо решение этой проблемы, не перемещая обратные вызовы в родительский класс и проверяя тип класса?

Greetz Mario

Ответы [ 2 ]

0 голосов
/ 24 декабря 2010

Итак, я решил свои проблемы после перемещения переменных моего экземпляра на @attributes и использования моего второго подхода для модуля STI:

module STI
  def new(*args, &block)
    type = args.dup.extract_options!.with_indifferent_access.delete(:type)
    if type.blank? or (type = type.constantize) == self
      super(*args, &block)
    else
      super(*args, &block).becomes(type)
    end
  end
end

class User < AR:Base
  extend STI

  belongs_to :account

  validates :password, :presence => true, :length => 8..40
  validates :password_digest, :presence => true

  def password=(password)
    @attributes['password'] = password
    self.password_digest = BCrypt::Password.create(password)
  end

  def password
    @attributes['password']
  end

  class Standard < User
    after_save :some_callback
  end
end

Теперь моя переменная экземпляра (пароль) копируется вновый User::Standard объект и обратные вызовы и проверки работают.Ницца!Но это обходной путь, а не исправление.;)

0 голосов
/ 23 декабря 2010

Может быть, есть кое-что, чего я не знаю, но я никогда не видел, чтобы классы Rails STI определялись таким образом. Обычно это выглядит как ...

приложение / модели / user.rb:

class User < AC::Base
  belongs_to :account
end

приложение / модели / пользователи / standard.rb:

module Users
  class Standard < User
    before_save :some_callback
  end
end

приложение / модели / пользователи / other.rb:

module Users
  class Other < User
  end
end

Похоже, что вы связываете область видимости класса (где класс «живет» по отношению к другим классам, модулям, методам и т. Д.) С наследованием класса (обозначается как «класс Standard <Пользователь»). Отношения Rails STI включают наследование, но не заботятся о сфере действия. Возможно, вы пытаетесь достичь чего-то очень специфического, вкладывая унаследованные классы, и я просто упускаю это. Но если нет, возможно, это вызывает некоторые из ваших проблем. </p>

Теперь перейдем конкретно к обратным вызовам. Обратный вызов в Standard не вызывается, потому что отношение account.users использует класс User, а не класс Standard (но я думаю, что вы уже это знаете). Есть несколько способов справиться с этим (я буду использовать мою структуру классов в примерах):

One:

class Account
  has_many :users, :class_name => Users::Standard.name
end

Это заставит всех account.users использовать класс Standard. Если вам нужны возможности других пользователей, то ...

Два:

class Account
    has_many :users # Use this to look up any user
    has_many :standard_users, :class_name => Users::Standard.name # Use this to look up/create only Standards
    has_many :other_users, :class_name => Users::Other.name # Use this to look up/create only Others
end

Три:

Просто вызовите Users :: Standard.create () и Users :: Other.create () вручную в своем коде.

Я уверен, что есть много других способов сделать это, но, возможно, есть самые простые.

...