2

Hi this question is basically the same as this one, which had no responses. I'm trying to combine the Devise registration form to include fields that produce not only a "user", but a "customer" object, an "account" object for that customer, and an "address" for that customer.

When visitor clicks "Sign Up", the registration form should include the standard Devise stuff, but also the fields for the creation of the other objects. Instead, I get this error:

NoMethodError in Registrations#new

undefined method `build_address' for # Extracted source (around line #6):

    <div class="panel panel-default" style="width: 14em;">  
 <% resource.build_customer if resource.customer.nil? %>
 <% resource.build_account if resource.accounts.nil? %>
 <% resource.build_address if resource.address.nil? %>
 <%= form_for(resource, as: resource_name, url: >registration_path(resource_name)) do |f| %>
   <%= devise_error_messages! %>
   <h3>User Info</h3>

Rather than explaining all the relationships, here are the models:

user.rb

    class User < ActiveRecord::Base
      before_create :generate_id

      # Virtual attribute for authenticating by either username or email
      # This is in addition to a real persisted field like 'username'
      attr_accessor :login

      has_one :administrator

      has_one :customer
      has_many :accounts, through: :customer
      accepts_nested_attributes_for :customer, :allow_destroy => true
      accepts_nested_attributes_for :accounts, :allow_destroy => true

      has_one :address, through: :customer
      accepts_nested_attributes_for :customer, :allow_destroy => true
      accepts_nested_attributes_for :address, :allow_destroy => true

      validates_uniqueness_of :email, :case_sensitive => false
      validates_uniqueness_of :id
      validates :username,
        :presence => true,
        :uniqueness=> {
            :case_sensitive => false
        }

      # User ID is a generated uuid
      include ActiveUUID::UUID
      natural_key :user_id, :remember_created_at
      belongs_to :user
      # specify custom UUID namespace for the natural key
      uuid_namespace "1dd74dd0-d116-11e0-99c7-5ac5d975667e"

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

      # Generate a random uuid for new user id creation
      def generate_id
        self.id = SecureRandom.uuid
      end

      # Allow signin by either email or username ("lower" function might have to be removed?)
      def self.find_for_database_authentication(warden_conditions)
          conditions = warden_conditions.dup
          if login = conditions.delete(:login)
            where(conditions.to_h).where(["lower(username) = :value OR         lower(email) = :value", { :value => login.downcase }]).first
          else
            where(conditions.to_h).first
          end
        end  
    end

customer.rb

   class Customer < ActiveRecord::Base
        belongs_to :user
        has_one :address
        has_many :accounts

        validates :phone1, :firstname, :lastname, presence: true
   end 

account.rb

    class Account < ActiveRecord::Base
        belongs_to :customer
        belongs_to :user
        has_one :acct_type
        has_many :acct_transactions

        validates :acct_type, presence: true
    end

address.rb

     class Address < ActiveRecord::Base
       belongs_to :customer
       belongs_to :user

       validates :zip_code, presence: true
       validates :address1, presence: true

       has_one :zip_code
       has_one :state, through: :zip_code
     end

The two controllers in question: registrations_controller.rb

    class RegistrationsController < Devise::RegistrationsController

        before_filter :configure_permitted_parameters

        # GET /users/sign_up
      def new
        @user = current_user
        @customer = nil #@user.customer
        @account = nil #@customer.account
        @address = nil #@customer.address

        # Override Devise default behavior and create a customer, account, and address as well
        build_resource({})
            resource.build_customer
        respond_with self.resource

        build_resource({})
            resource.build_account
        respond_with self.resource

        build_resource({})
            resource.build_address
        respond_with self.resource

      end

      protected

      def configure_permitted_parameters
        devise_parameter_sanitizer.for(:sign_up) { |u|
          .permit(:username, :email, :password, :password_confirmation,
                   :customer_attributes => [:phone1, :phone2, :title, :firstname, :lastname],
                   :account_attributes => :acct_type,
                   :address_attributes => [:address1, :address2, :zip_code])
        }
      end
    end

addresses_controller.rb (The important parts)

      def new
        @customer = current_user.customer
        @address = @customer.address.build(:customer_id => @customer.id,
                                  :address1 => nil,
                                  :address2 => nil,
                                  :zip_code => nil)
      end

      def create
        @customer = current_user.customer
        @address = @customer.address.build(:customer_id => @customer.id,
                                  :address1 => nil,
                                  :address2 => nil,
                                  :zip_code => nil)

        respond_to do |format|
          if @address.save
            format.html { redirect_to @address, notice: 'Address was successfully created.' }
            format.json { render :show, status: :created, location: @address }
          else
            format.html { render :new }
            format.json { render json: @address.errors, status: :unprocessable_entity }
          end
        end
      end

And here is the view where the exception is raised (It's really long so actually the important parts):

    <h1>Create an account</h1>
    <div class="form-group">
    <div class="panel panel-default" style="width: 14em;">  
        <% resource.build_customer if resource.customer.nil? %>
        <% resource.build_account if resource.accounts.nil? %>
        <% resource.build_address if resource.address.nil? %>
        <%= form_for(resource, as: resource_name, url:         registration_path(resource_name)) do |f| %>
          <%= devise_error_messages! %>
          <h3>User Info</h3>
            <div class="form-group">
              <!-- fields for User object -->
              <div class="field">
                <%= f.label :username %><br />
                <%= f.text_field :username, autofocus: true  %>
              </div>
    ...
    <!-- fields for Customer object -->
      <%= f.fields_for :customer do |customer_fields| %>
        <div class="field">
          <%= customer_fields.label :firstname %>
          <%= customer_fields.text_field :firstname %>
        </div>
    ...
    <!-- fields for Account object -->
      <%= f.fields_for :account do |account_fields| %>
        <div class="field">
          <%= account_fields.label :acct_type %>
          <%= account_fields.text_field :acct_type %>
        </div>
      <% end %>

    <!-- fields for Address object -->
      <%= f.fields_for :address do |address_fields| %>
        <div class="field">
          <%= address_fields.label :address1 %>
          <%= address_fields.text_field :address1 %>
        </div>
    ...

The exception is pointing to the block of statements at the top...

    <% resource.build_customer if resource.customer.nil? %>
    <% resource.build_account if resource.accounts.nil? %>
    <% resource.build_address if resource.address.nil? %>

... which has given me trouble before. Before the current error I was getting a similar error from the second line ("build_account"). But that turned out to be a pluralization issue, which I believe I've fixed. Since the HTML is read sequentially, it would seem that there is no problem with the first two build_ methods. Why is there then a problem with the build_address method?

I need to fix this error before I can know if the whole thing will actually work or not. Any ideas?

Thanks It's Rails 4.1.8 / Devise 3.4.1

Community
  • 1
  • 1
B. Bulpett
  • 814
  • 12
  • 27

1 Answers1

1

The trouble turned out to be the syntax I was using create multiple resource objects. It would pass one, but ignored the rest. What I ended up doing to make it work (or at least make the error go away) was to override the build_resource method to accept an array of parameters for each object to be instantiated:

    def new
        @user = current_user

        build_resource({})
            self.resource[:customer => Customer.new, :account => Account.new, :address => Address.new]
        respond_with self.resource
      end

      def build_resource(hash=nil)
            hash ||= params[resource_name] || {}
            self.resource = resource_class.new(hash)
      end

      def create
        # Override Devise default behavior and create a customer, account, and address as well
        resource = build_resource(params[:user])

        if(resource.save)
          sign_in(resource_name, resource)
          respond_with resource, :location => after_sign_up_path_for(resource)
        else
          render :action => "new"
        end
      end

Also, I removed the three lines at the top of the form view as they attempted to do some sort of pre-validation in the view and just caused problems. Plenty of validation will happen when the form is submitted. This seems to be doing something good. Now I'm working with the form view and having trouble getting each part to render. Fields_for is rendering fields for User and Account models, but not Customer or Address.

B. Bulpett
  • 814
  • 12
  • 27