Home > database >  How to properly make a callback to create a model, after initialize of another model?
How to properly make a callback to create a model, after initialize of another model?

Time:02-05

To start, I may be using the wrong callback for what I'm trying to do

and/or there may be a better "Rails Way" to accomplish this. If so, I'm all ears.

I've been referencing api.rubyonrails.org/v7.0.1/classes/ActiveRecord/Callbacks/ClassMethods

and

guides.rubyonrails.org/active_record_callbacks.html#creating-an-object

I need to implement:

"user sign up creates an account associated with user as owner"

(ultimately, user will belong_to account, and account will has_many users)

Accounts in database schema have name:string and owner_id:integer

Currently, I'm not able to access the newly created User, and the Account is not being created. I think I'm very close though.

current models/user.rb:

class User < ApplicationRecord
  after_save :create_account
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable


  private

  def create_account
    Account.create(owner_id: user.id, name: "#{user.name}'s Account")
    redirect_to root_path
  end
end

outputs to the stacktrace:

Started POST "/users" for ::1 at 2022-02-04 23:39:40 -0600
Processing by RegistrationsController#create as TURBO_STREAM
  Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"name"=>"tester", "phonenumber"=>"1234567890", "email"=>"[email protected]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Sign up"}
  TRANSACTION (0.2ms)  BEGIN
  User Exists? (1.1ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "[email protected]"], ["LIMIT", 1]]
  User Create (0.6ms)  INSERT INTO "users" ("email", "encrypted_password", "reset_password_token", "reset_password_sent_at", "remember_created_at", "created_at", "updated_at", "name", "phonenumber") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING "id"  [["email", "[email protected]"], ["encrypted_password", "[FILTERED]"], ["reset_password_token", "[FILTERED]"], ["reset_password_sent_at", "[FILTERED]"], ["remember_created_at", nil], ["created_at", "2022-02-05 05:39:40.493526"], ["updated_at", "2022-02-05 05:39:40.493526"], ["name", "tester"], ["phonenumber", "1234567890"]]
  TRANSACTION (0.2ms)  ROLLBACK
Completed 500 Internal Server Error in 298ms (ActiveRecord: 2.1ms | Allocations: 9928)



NameError (undefined local variable or method `user' for #<User id: nil, email: "[email protected]", created_at: "2022-02-05 05:39:40.493526000  0000", updated_at: "2022-02-05 05:39:40.493526000  0000", name: "tester", phonenumber: "1234567890">
Did you mean?  super):

app/models/user.rb:12:in `create_account'

CodePudding user response:

As prasannaboga already wrote in his comment. Callbacks like these are running in the context of an instance. That means you do not need to call a specific user first because the current object is already that user.

Additionally, in the context of a model, the redirect_to call doesn't make any sense. Models don't know anything about requests and responses. redirect_to is a controller method and you need to add it to your controller.

The following callback method should work:

def create_account Account.create(owner_id: id, name: "#{name}'s Account") end

I wonder why you decided to not add an has_one :account or has_many :accounts association to your User model? By doing so you could simplify the method even more because then Rails would handle setting the id automatically, like this

build_account(name: "#{name}'s Account")  # when `has_one` or
accounts.build(name: "#{name}'s Account") # when `has_many`
  •  Tags:  
  • Related