Rails rollback when creating user with nested forms

匆匆过客 提交于 2019-12-20 03:52:09

问题


I'm getting a rollback when trying to add a user to my DB and I'm not sure why.

I have three models: company.rb

class Company < ActiveRecord::Base
 acts_as_paranoid

 has_many :locations, dependent: :destroy
 has_many :users, dependent: :destroy
 has_many :tests, through: :locations
 has_many :reports, dependent: :destroy

 accepts_nested_attributes_for :locations, :users

 validates_presence_of :name
end

** user.rb **

class User < ActiveRecord::Base
 acts_as_paranoid

 devise :database_authenticatable,
      :recoverable,
      :rememberable,
      :trackable,
      :validatable,
      :registerable

 belongs_to :company
 has_and_belongs_to_many :roles
end

** location.rb **

class Location < ActiveRecord::Base
 acts_as_paranoid

 belongs_to :company
 has_many :network_hosts, dependent: :destroy
 has_many :tests, dependent: :destroy
 has_many :commands, dependent: :destroy

 validates_presence_of :company, :identifier, :name
 validates_uniqueness_of :identifier

 delegate :security_percentage, to: :last_test, allow_nil: true

 after_initialize :generate_identifier, if: -> { self.identifier.blank? }

 def generate_identifier
  self.identifier = SecureRandom.uuid.delete("-")
end

So, when a user wants to signup they need to enter company, location, and user information, which is controlled by my company_controller.rb

** company_controller.rb **

class CompanyController < ApplicationController
 def new
  @company = Company.new
  1.times { @company.locations.build }
  1.times { @company.users.build }
 end

 def create
  @company = Company.new(company_params)
  if @company.save
   redirect_to root_url
  else
   render :new
  end
 end

 private

  def company_params
   params.require(:company).permit(:name, locations_attributes: [:name], users_attributes: [:first_name, :last_name, :full_name, :email, :password, :password_confirmation])
  end
end

The form is using the standard form_for with nested attributes so that I can hopefully make everything in one shot when the user clicks the submit button

** company/new.html.erb **

<%= form_for @company, :url => url_for( :controller => 'company', :action => 'new' ) do |f| %>
  <div class="form-group">
    <%= f.label "Company Name" %>
    <%= f.text_field :name, class: "form-control", placeholder: "ACME Inc."  %>

    <%= f.fields_for :locations do | location_builder | %>
      <%= location_builder.label "Location Name" %>
      <%= location_builder.text_field :name, class: "form-control", placeholder: "Main Building" %>

    <% end %>
  </div>

  <div class="form-group">
    <%= f.fields_for :users do | user_builder | %>
      <%= user_builder.label "First Name" %>
      <%= user_builder.text_field :first_name, class: "form-control", placeholder: "John" %>

      <%= user_builder.label "Last Name" %>
      <%= user_builder.text_field :last_name, class: "form-control", placeholder: "Smith" %>

      <%= user_builder.label "Full Name" %>
      <%= user_builder.text_field :full_name, class: "form-control", placeholder: "John Smith" %>

      <%= user_builder.label "Email" %>
      <%= user_builder.email_field :email, class: "form-control", placeholder: "john@acme.com" %>

      <%= user_builder.label "Password" %>
      <%= user_builder.password_field :password, class: "form-control" %>

      <%= user_builder.label "Confirm Password" %>
      <%= user_builder.password_field :password_confirmation, class: "form-control" %>
    <% end %>
  </div>

  <%= f.submit "Submit", class: "btn btn-large btn-success" %>
<% end %>

However, I'm getting a rollback in the logs that this isn't happening and can't figure out why.

Started POST "/signup" for 127.0.0.1 at 2015-08-07 13:49:22 -0400
Processing by CompanyController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"2OHwJ9UfEbfkZHjLdm9BfOd7jlRdvoEz0L4NRJCKl64=", "company"=>{"name"=>"ACME Brick", "locations_attributes"=>{"0"=>{"name"=>"Main House"}}, "users_attributes"=>{"0"=>{"first_name"=>"Testin", "last_name"=>"User", "full_name"=>"Test User", "email"=>"test@test.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}}}, "commit"=>"Submit"}
  User Load (0.8ms)  SELECT  "users".* FROM "users"  WHERE "users"."deleted_at" IS NULL AND "users"."id" = 7  ORDER BY "users"."id" ASC LIMIT 1
  Role Load (0.3ms)  SELECT "roles".* FROM "roles" INNER JOIN "roles_users" ON "roles"."id" = "roles_users"."role_id" WHERE "roles"."deleted_at" IS NULL AND "roles_users"."user_id" = $1  [["user_id", 7]]
   (0.1ms)  BEGIN
  Location Exists (0.3ms)  SELECT  1 AS one FROM "locations"  WHERE "locations"."identifier" = '3b7febb35ea740488788d43fcc5e989c' LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users"  WHERE "users"."email" = 'test@test.com' LIMIT 1
   (0.1ms)  ROLLBACK
  Rendered company/new.html.erb within layouts/application (5.0ms)
Completed 200 OK in 108ms (Views: 32.2ms | ActiveRecord: 1.9ms | Solr: 0.0ms)

** Company Table **

class CreateCompanies < ActiveRecord::Migration
  def change
    create_table :companies do |t|
      t.string :name

      t.timestamps
    end
  end
end

** Location Table **

class CreateLocations < ActiveRecord::Migration
  def change
    create_table :locations do |t|
      t.belongs_to :company, index: true
      t.string :identifier
      t.string :name

      t.timestamps
    end
  end
end

** User Table **

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table(:users) do |t|

      t.belongs_to :company, index: true
      
      t.string :username
      t.string :first_name
      t.string :last_name
      t.string :full_name

      t.string :time_zone, :default => "Central Time (US & Canada)"

      t.string :avatar_file_name
      t.string :avatar_content_type
      t.integer :avatar_file_size
      t.datetime :avatar_updated_at

      ## Database authenticatable
      t.string :email,              :null => false, :default => ""
      t.string :phone_number
      t.string :encrypted_password, :null => false, :default => ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, :default => 0
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Token authenticatable
      t.string :authentication_token

      t.timestamps
    end

    add_index :users, :email,                :unique => true
    add_index :users, :reset_password_token, :unique => true

    create_table :roles_users, :id => false do |t|
      t.references :role, :user
    end
  end

  def self.down
    drop_table :users
    drop_table :roles_users
  end
end

** The error from logs **

Started POST "/signup" for 127.0.0.1 at 2015-08-07 18:12:54 -0400
Processing by CompanyController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"aAc83zKKlV4w1i2GhTqTo3ehtXP+tPvYbBBRq1ccYzA=", "company"=>{"name"=>"Test Co.", "locations_attributes"=>{"0"=>{"name"=>"Main"}}, "users_attributes"=>{"0"=>{"first_name"=>"John", "last_name"=>"Smith", "full_name"=>"John Smith", "email"=>"john@acme.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}}}, "commit"=>"Submit"}
  User Load (0.5ms)  SELECT  "users".* FROM "users"  WHERE "users"."deleted_at" IS NULL AND "users"."id" = 7  ORDER BY "users"."id" ASC LIMIT 1
  Role Load (0.3ms)  SELECT "roles".* FROM "roles" INNER JOIN "roles_users" ON "roles"."id" = "roles_users"."role_id" WHERE "roles"."deleted_at" IS NULL AND "roles_users"."user_id" = $1  [["user_id", 7]]
   (0.1ms)  BEGIN
  Location Exists (0.3ms)  SELECT  1 AS one FROM "locations"  WHERE "locations"."identifier" = '0d759e5405084663a1c110d37f04573a' LIMIT 1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE "users"."email" = 'john@acme.com' LIMIT 1
   (0.1ms)  ROLLBACK
Completed 422 Unprocessable Entity in 75ms
** [Airbrake] Notice was not sent due to configuration:         
  Environment Monitored? false         
  API key set? true

ActiveRecord::RecordInvalid (Validation failed: Locations company can't be blank):
  app/controllers/company_controller.rb:10:in `create'
  app/controllers/application_controller.rb:95:in `set_time_zone'


  Rendered /Users/godzilla/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/actionpack-4.1.7/lib/action_dispatch/middleware/templates/rescues/_source.erb (0.9ms)
  Rendered /Users/godzilla/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/actionpack-4.1.7/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (1.7ms)
  Rendered /Users/godzilla/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/actionpack-4.1.7/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.2ms)
  Rendered /Users/godzilla/.rbenv/versions/2.1.4/lib/ruby/gems/2.1.0/gems/actionpack-4.1.7/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (15.6ms)

It references that the location identifier already exists, but it doesn't. This is something that is made on the fly when trying to create a new Location (notice the method generate_identifier in the location.rb model). On top of that, the user doesn't exist either.

Any ideas how to get around this problem?


回答1:


When you write validates_presence_of :company it means that your company record has to exist at the time of location creation, but it is not fully saved yet. However, your location is still associated with the company object, and it will be saved correctly without this validation. I think you can validate on presence of company_id instead because company id becomes available during saving.



来源:https://stackoverflow.com/questions/31884008/rails-rollback-when-creating-user-with-nested-forms

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!