3

I am quite new to Ruby and was wondering how should I do if I want that every time I create a new User (when signing up), it automatically creates an instance of Contractor so that the new User = Contractor.

I am quite lost and some help and explanations would be really nice to have.

Users Controller

each_pair { |name, val|  }class UsersController < ApplicationController
  before_action :configure_permitted_parameters, if: :devise_controller?
  before_action :update_resource_params, if: :devise_controller?

  def new
    @user = User.new
  end

  def create
    @user = User.new(user.params)
    @contractor = Contractor.create(user: @user)
    if @user.save
      UserMailer.user_alert(@user).deliver_now
      redirect_to @user, notice: 'User was successfully created.'
    else
      render :new
    end
  end

Contractor Model

class Contractor < ApplicationRecord
  belongs_to :user
  has_many :construction_projects, dependent: :destroy
  has_many :clients, dependent: :destroy
  has_many :documents, through: :construction_projects
  has_many :resources
  mount_uploader :logo, LogoUploader

  # Model validations
  validates :user, presence: true
  validates_associated :construction_projects
  validates_associated :clients
  validates_associated :documents
  validates_associated :resources

User Model

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

  has_one :contractor
  has_many :clients, through: :contractor
  has_many :construction_projects, through: :contractor

  # Model validations
  validates_associated :clients
  validates_associated :construction_projects

  validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }

  def option_projects
   projects = self.contractor.construction_projects.map{ |cp| [cp.name, cp.id] }
   projects << ["Add a new project", "Add a new project"]
   projects
  end
end
3
  • Why would you create a separate Contract model/object if they both present the same entity? Commented Nov 19, 2018 at 14:38
  • I forgot to mention but I am using the devise gem for the registrations of new users Commented Nov 19, 2018 at 14:44
  • @tahirwaseer What do you mean with that? Could you explain a bit more what you are telling me to do? Thanks Commented Nov 19, 2018 at 14:47

2 Answers 2

2

You've defined that a user has_one contractor, which means that the contractor has the foreign key (the user_id). You're creating the contractor like this:

@user = User.new(user.params)
@contractor = Contractor.create(user: @user)

Rails needs to create contractor row with a user_id referencing the new user, which hasn't been saved yet. Rails goes ahead and tries to save the new user (so it gets an id) and then creates the contractor.

You aren't checking for any validation errors here, so it's possible that this will create a contractor but not a user.

It looks like you don't need to pass any attributes when you first create the contractor (other than the user). So I would change your code to this:

@user = User.new(user.params)   # should that be user_params?
if @user.save
  # now we have a saved user, so create its associated Contractor
  @user.create_contractor!
  ...

The create_contractor! method is automatically defined for you when you declare the has_one association. The ! version of the method will raise an exception if there is some error creating the contractor. That seems reasonable if you don't have any method of handling such an error here (and don't expect it under normal circumstances).

Sign up to request clarification or add additional context in comments.

4 Comments

This answer is less than ideal as you can just use the build_contractor method to add the association in memory before saving. This means that you can ovoid having a second code path or unhandled exception just because a validation fails.
@max, in my experimentation, using build_contractor would not save the Contractor object (via @user.save) if there was a validation error in the contractor, and the return value from @user.save (or save!) would be true, giving you no direct indication that the contractor was not created. My solution addresses that. Not sure what you mean by "second code path"; there isn't one. I also addressed the issue of the exception in my response.
You would need a second code path if you actually handle the fact that the second save may fail.
And you need to use validates_associated to make the save conditional on the other records validations.
0

You need to start by setting up an association from user to contractor:

class User < ApplicationRecord
  # ...
  has_one :contractor
  # or
  has_many :contractors
end

You can then insert both records at once by building one off the association:

irb(main):001:0> @user = User.new
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> @user.build_contractor
=> #<Contractor id: nil, user_id: nil, created_at: nil, updated_at: nil>
irb(main):003:0> @user.save
   (0.3ms)  BEGIN
  User Create (0.8ms)  INSERT INTO "users" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id"  [["created_at", "2018-11-19 17:43:37.730785"], ["updated_at", "2018-11-19 17:43:37.730785"]]
  Contractor Create (1.9ms)  INSERT INTO "contractors" ("user_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["user_id", 2], ["created_at", "2018-11-19 17:43:37.734395"], ["updated_at", "2018-11-19 17:43:37.734395"]]
   (1.2ms)  COMMIT

ActiveRecord is smart enough that it inserts the records in the correct order so that the id returned is used for contractors.user_id.

If the association is has_many you can do the same thing by calling .new on the association:

@user.contractors.new

See:

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.