【问题标题】:rspec + factorygirl : stack level too deeprspec + factorygirl:堆栈级别太深
【发布时间】:2015-04-01 18:35:33
【问题描述】:

我有一个 Account 和一个 User 模型。使用Account: has_one :owner, class_name: 'User'has_many :users

我的第一个规范失败了:

 1) User has a valid factory
 Failure/Error: Unable to find matching line from backtrace
 SystemStackError:
   stack level too deep
 # /usr/local/rvm/gems/ruby-2.1.1/gems/activerecord-4.1.1/lib/active_record/connection_adapters/abstract/database_statements.rb:222

我正在寻找一个循环,但我没有看到它!用户工厂调用调用所有者工厂的帐户工厂......有什么问题吗?

## /spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, :type => :model do
  it "has a valid factory" do
    expect(FactoryGirl.create(:user)).to be_valid
  end
end

我的 2 家工厂:

## /spec/factories/accounts.rb
require 'faker'
FactoryGirl.define do
  factory :account do
    name { Faker::Company.name }
    subdomain { Faker::Internet.domain_word }
    association :owner, factory: :owner, strategy: :build
  end
end

## /spec/factories/users.rb
require 'faker'
FactoryGirl.define do
  factory :user do
    firstname { Faker::Name.first_name }
    lastname { Faker::Name.last_name }
    email { Faker::Internet.email }
    password { Faker::Internet.password(6) }
    association :account, factory: :account, strategy: :build
    role {"employee"}
  end

  factory :owner, class: User do
    firstname { Faker::Name.first_name }
    lastname { Faker::Name.last_name }
    email { Faker::Internet.email }
    password { Faker::Internet.password(6) }
    role {"owner"}
  end
end

编辑,添加 2 个模型:

class User < ActiveRecord::Base
  include ActiveModel::Dirty
  include PublicActivity::Common

  ##
  # user ROLES can be owner, manager or employee
  ROLES =%w[owner manager employee]

  devise :invitable, :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable

  acts_as_messageable
  acts_as_tenant :account
  accepts_nested_attributes_for :account

  ######################################################################
  # Relations                                                          #
  ######################################################################
  belongs_to     :team
  has_many       :posts
  has_many       :shifts

  ######################################################################
  # Validations                                                        #
  ######################################################################
  validates :lastname,  presence: true, allow_nil: false
  validates :firstname, presence: true, allow_nil: false
  validates :email,     presence: true, allow_nil: false
  validates_uniqueness_of :email, scope: :account_id
  validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
  validates :password,
            presence: true,
            length: { :in => 2..20 },
            :if => :password_required?, unless: :skip_password_validation
  validates :role, presence: true

  ######################################################################
  # Callbacks                                                          #
  ######################################################################

  before_save   :delete_avatar, if: -> { remove_avatar == '1' && !avatar_updated_at_changed? }
  before_create :default_values

  ######################################################################
  # Scope                                                              #
  ######################################################################
  scope :active,               -> (active)     { where active: active }
  scope :active_without_owner, ->              { active(true).where.not(role: 'owner') }
  scope :active_grouped,       ->              { where(active: true).group("id","team_id") }
  scope :team,                 -> (team_id)    { where team_id: team_id }
  scope :starts_with,          -> (lastname)   { where("lastname like ?", "#{lastname}%") }
  scope :sort_lastname,        ->              { order("users.lastname ASC") }
  scope :account_id,           ->  { where account_id: account_id }

  attr_accessor  :is_manager, :last_current_user, :skip_password_validation

  def activebydefault
    self.active = true if self.active.nil?
  end

private

  def default_values
    self.active ||= 'true'
    self.save
    true
  end

end


class Account < ActiveRecord::Base

  RESTRICTED_SUBDOMAINS = %w(www)

  ######################################################################
  # Relations                                                          #
  ######################################################################
  has_one :owner, class_name: 'User'
  has_many :users
  has_many :teams
  accepts_nested_attributes_for :owner, :teams

  ######################################################################
  # Validations                                                        #
  ######################################################################
  validates :owner, presence: true
  validates :name, presence: true
  validates :subdomain, presence: true,
    uniqueness: { case_sensitive: false, message: 'already used' },
    format: { with: /\A[\w\-]+\Z/i, message: 'contains invalid characters' },
    exclusion: { in: RESTRICTED_SUBDOMAINS, message: 'restricted' }
  validates :time_zone, allow_nil: true,
    inclusion: {
    in: ActiveSupport::TimeZone.zones_map(&:name).keys
    }

  ######################################################################
  # Callbacks                                                          #
  ###################################################################### 
  before_validation :downcase_subdomain
  after_create :add_owner_to_team, :default_values, :send_notification


private
  def downcase_subdomain
     self.subdomain = subdomain.try(:downcase)
  end

  def default_values
    self.publish_to_wall_new_employee = true
    self.publish_to_wall_published_schedule = true
    self.publish_to_wall_modified_schedule = true
    self.publish_to_wall_new_document = true
    self.save
    true
  end

end

编辑 2:添加尾部

  ActiveRecord::SchemaMigration Load (0.4ms)  SELECT "schema_migrations".* FROM "schema_migrations"
   (0.2ms)  BEGIN
   (0.3ms)  SAVEPOINT active_record_1
  User Exists (1.0ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" IS NULL) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" IS NULL) LIMIT 1
  Account Exists (0.5ms)  SELECT  1 AS one FROM "accounts"  WHERE LOWER("accounts"."subdomain") = LOWER('jean') LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'thomas.benoit@bourgeois.net' AND "users"."account_id" IS NULL) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'thomas.benoit@bourgeois.net' AND "users"."account_id" IS NULL) LIMIT 1
  SQL (0.4ms)  INSERT INTO "accounts" ("created_at", "name", "subdomain", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["created_at", "2015-02-02 19:58:20.000241"], ["name", "Pierre EURL"], ["subdomain", "jean"], ["updated_at", "2015-02-02 19:58:20.000241"]]
  User Exists (0.4ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = 'lola.francois@legrand.com' AND "users"."account_id" = 131) LIMIT 1

走啊走啊……

【问题讨论】:

  • 我觉得你的模型中一定有什么东西导致了无限级别的递归。您在那里指定的三个模型中的任何一个在保存回调之前或之后是否有创建另一个模型实例的回调?可以随时尝试拖尾log/test.log文件,看看它是否在停止之前输出sql语句或详细信息。
  • @agmcleod 我已经添加了 2 个模型声明。
  • @agmcleod - 我在我的问题中添加了尾巴。也许我没有理解正确,但是前两个用户的account_id为null,而后创建的帐户的id为131。FactoryGirl中的关联不应该为所有人设置相同的account_id吗?
  • 您已经设置了建立帐户的策略,所以它在内存中。当用户被保存时,它不会自动保存帐户。我不得不承认我不确定工厂女孩的内部结构是如何工作的。如果您使用 user.account.build(params),并在用户上调用 save。两者都会得救。不知道FG是否这样做。你也可以使用api.rubyonrails.org/classes/ActiveRecord/…,但要小心。

标签: ruby-on-rails rspec factory-bot


【解决方案1】:

我想这只是因为before_create回调:

User模特:

  def default_values
    self.active ||= 'true'
    self.save
    true
  end

尝试替换为:

  def default_values
    self.active ||= 'true'
  end

您不需要手动调用save(因为它正在循环您的回调)

【讨论】:

  • 谢谢,就是这样。仍然在摸索为什么它会循环。是不是因为它保存了一个尚未创建的对象?
  • 是的。在 before_create 或 before_save 或 before_validate 中,您永远不想自己调用 save。反正它会去。此外,默认值通常在迁移中得到更好的处理。当您使用 add_columncreate_table 时,您可以添加默认值。因此,如果 nil 被传递,它将具有该值。例如:pastebin.com/tMxAvUdc
猜你喜欢
  • 1970-01-01
  • 2012-03-04
  • 2013-10-05
  • 2016-09-10
  • 2012-11-30
  • 1970-01-01
  • 1970-01-01
  • 2011-11-17
  • 2012-07-24
相关资源
最近更新 更多