1
votes

I have an Account and a User models. With Account: has_one :owner, class_name: 'User' and has_many :users.

My first spec is failing :

 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

I am looking for a loop, but I don't see it ! The user factory calls the account factory which calls the owner factory... anything wrong with that ?

## /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

My 2 factories :

## /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

EDIT, adding the 2 models :

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

EDIT 2 : added the tail

  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" = '[email protected]' AND "users"."account_id" IS NULL) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' 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" = '[email protected]' AND "users"."account_id" IS NULL) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' 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" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE ("users"."email" = '[email protected]' AND "users"."account_id" = 131) LIMIT 1

and goes and goes...

1
i feel like there must be something in your models causing an infinite level of recursion. Do any of the three models you specified there have before or after save callbacks that create another instance of a model? Can always try tailing the log/test.log file and see if it outputs sql statements or details before it stops. - agmcleod
@agmcleod I've added the 2 models declarations. - Olivier Severyns
@agmcleod - I added the tail, in my question. Maybe I don't understand correctly, but the first 2 users have an account_id null, when the account created after has an id of 131. Shouldn't the association in FactoryGirl set the same account_id for all ? - Olivier Severyns
You have the strategy set to build the account, so it is in memory. When the user gets saved, it does not automatically save the account. I have to admit i'm not sure on how the internals of factory girl work. If you use user.account.build(params), and invoke save on the user. Both will be saved. No idea if FG does that. You can also use api.rubyonrails.org/classes/ActiveRecord/…, but be careful with it. - agmcleod

1 Answers

0
votes

I suppose it happens just because of before_create callback:

User model:

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

try replace to:

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

you don't need call save manually (because it's looping your callback)