How to create another object when creating a Devise User from their registration form in Rails?

后端 未结 2 1434
我在风中等你
我在风中等你 2021-02-10 18:38

There are different kinds of users in my system. One kind is, let\'s say, a designer:

class Designer < ActiveRecord::Base
  attr_accessible :user_id, :portfol         


        
2条回答
  •  轻奢々
    轻奢々 (楼主)
    2021-02-10 19:16

    In a recent project I've used the form object pattern to create both a Devise user and a company in one step. This involves bypassing Devise's RegistrationsController and creating your own SignupsController.

    # config/routes.rb
    # Signups
    get 'signup' => 'signups#new',     as: :new_signup
    post 'signup' => 'signups#create', as: :signups  
    
    
    # app/controllers/signups_controller.rb
    class SignupsController < ApplicationController
      def new
        @signup = Signup.new
      end
    
      def create
        @signup = Signup.new(params[:signup])
    
        if @signup.save
          sign_in @signup.user
          redirect_to projects_path, notice: 'You signed up successfully.'
        else
          render action: :new
        end
      end
    end
    

    The referenced signup model is defined as a form object.

    # app/models/signup.rb
    
    # The signup class is a form object class that helps with
    # creating a user, account and project all in one step and form
    class Signup
      # Available in Rails 4
      include ActiveModel::Model
    
      attr_reader :user
      attr_reader :account
      attr_reader :membership
    
      attr_accessor :name
      attr_accessor :company_name
      attr_accessor :email
      attr_accessor :password
    
      validates :name, :company_name, :email, :password, presence: true
    
      def save
        # Validate signup object
        return false unless valid?
    
        delegate_attributes_for_user
        delegate_attributes_for_account
    
        delegate_errors_for_user unless @user.valid?
        delegate_errors_for_account unless @account.valid?
    
        # Have any errors been added by validating user and account?
        if !errors.any?
          persist!
          true
        else
          false
        end
      end
    
      private
    
      def delegate_attributes_for_user
        @user = User.new do |user|
          user.name = name
          user.email = email
          user.password = password
          user.password_confirmation = password
        end
      end
    
      def delegate_attributes_for_account
        @account = Account.new do |account|
          account.name = company_name
        end
      end
    
      def delegate_errors_for_user
        errors.add(:name, @user.errors[:name].first) if @user.errors[:name].present?
        errors.add(:email, @user.errors[:email].first) if @user.errors[:email].present?
        errors.add(:password, @user.errors[:password].first) if @user.errors[:password].present?
      end
    
      def delegate_errors_for_account
        errors.add(:company_name, @account.errors[:name].first) if @account.errors[:name].present?
      end
    
      def persist!
        @user.save!
        @account.save!
        create_admin_membership
      end
    
      def create_admin_membership
        @membership = Membership.create! do |membership|
          membership.user = @user
          membership.account = @account
          membership.admin = true
        end
      end
    end
    

    An excellent read on form objects (and source for my work) is this CodeClimate blog post on Refactoring.

    In all, I prefer this approach vastly over using accepts_nested_attributes_for, though there might be even greater ways out there. Let me know if you find one!

    ===

    Edit: Added the referenced models and their associations for better understanding.

    class User < ActiveRecord::Base
      # Memberships and accounts
      has_many :memberships
      has_many :accounts, through: :memberships
    end
    
    class Membership < ActiveRecord::Base
      belongs_to :user
      belongs_to :account
    end
    
    class Account < ActiveRecord::Base
      # Memberships and members
      has_many :memberships, dependent: :destroy
      has_many :users, through: :memberships
      has_many :admins, through: :memberships,
                        source: :user,
                        conditions: { 'memberships.admin' => true }
      has_many :non_admins, through: :memberships,
                            source: :user,
                            conditions: { 'memberships.admin' => false }
    end
    

    This structure in the model is modeled alongside saucy, a gem by thoughtbot. The source is not on Github AFAIK, but can extract it from the gem. I've been learning a lot by remodeling it.

提交回复
热议问题