开发者

ActiveRecord Problems using callbacks and STI

开发者 https://www.devze.com 2023-02-01 03:19 出处:网络
Hey folks, following problem with Rails and STI: I have following classes: class Account < AC::Base has_many :users

Hey folks, following problem with Rails and STI:

I have following classes:

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

And now the problem: Without rewriting User.new (in module STI), the callback inside User::Stan开发者_Python百科dard gets never called, otherwise the account_id is always nil if I create users this way:

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

If I'm using a different approach for the module like:

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

Then instance variables are not shared, because it's creating a new object. Is there any solution for this problem without moving the callbacks to the parent class and checking the type of class?

Greetz Mario


Maybe there's something I don't know, but I've never seen Rails STI classes defined in that manner. Normally it looks like...

app/models/user.rb:

class User < AC::Base
  belongs_to :account
end

app/models/users/standard.rb:

module Users
  class Standard < User
    before_save :some_callback
  end
end

app/models/users/other.rb:

module Users
  class Other < User
  end
end

It looks as though you are conflating class scope (where a class "lives" in relation to other classes, modules, methods, etc.) with class inheritance (denoted by "class Standard < User"). Rails STI relationships involve inheritance but do not care about scope. Perhaps you are trying to accomplish something very specific by nesting inherited classes and I am just missing it. But if not, it's possible it's causing some of your issues.

Now moving on to the callbacks specifically. The callback in Standard isn't getting called because the "account.users" relationship is using the User class, not the Standard class (but I think you already know that). There are several ways to deal with this (I will be using my class structure in the examples):

One:

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

This will force all account.users to use the Standard class. If you need the possibility of Other users, then...

Two:

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

Three:

Just call Users::Standard.create() and Users::Other.create() manually in your code.

I'm sure there are lots of other ways to accomplish this, but there are probably the simplest.


So I solved my problems after moving my instance variables to @attributes and using my second approach for the module 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

Now my instance variable (the password) is copied to the new User::Standard object and callbacks and validations are working. Nice! But it's a workaround, not really a fix. ;)

0

精彩评论

暂无评论...
验证码 换一张
取 消