Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby on Rails Active Record return value when create fails?

I am new to ruby on rails and having trouble getting this work. Basically I have a user registration page which has a password confirmation. In the User class I have the following validation:

validates :password, confirmation: true

And in the controller I have

def create
    vals = params[:user]
    if(User.exists(vals[:username])) 
        flash[:warning] = "#{vals[:username]} already exists! Please try a new one. "
    else
        vals[:create_date] = DateTime.current
        user = User.create(vals, :without_protection => :true)
        if user==false or user==nil or user==vals
            flash[:warning] = "#{vals[:username]} has not been registered successfully. "
        else
            flash[:notice] = "#{vals[:username]} has been registered. "
        end
    end
    redirect_to users_path
end

The problem is that when the password does match the confirmation, I am still getting the notice message showing that the registration is successful. As you can see I have tried several return values for create but none of them seems to succeed. I am pretty sure that the validation is working because I cannot see the user that I just created if password does not match with confirmation. In addition, when I use create!, I can see the website crashes with the validation error. Can anyone help telling me what create should return when the record is not validated?

Thanks.

like image 464
zyl1024 Avatar asked Jun 01 '14 00:06

zyl1024


People also ask

What does ActiveRecord return?

Active Record objects can be created from a hash, a block, or have their attributes manually set after creation. The new method will return a new object while create will return the object and save it to the database. A call to user.

What does .save do in Ruby?

The purpose of this distinction is that with save! , you are able to catch errors in your controller using the standard ruby facilities for doing so, while save enables you to do the same using standard if-clauses.

What does Update_all return?

ActiveRecord's update_all() returns the number of records updated.

What is ActiveRecord in Ruby on Rails?

What is ActiveRecord? ActiveRecord is an ORM. It's a layer of Ruby code that runs between your database and your logic code. When you need to make changes to the database, you'll write Ruby code, and then run "migrations" which makes the actual changes to the database.


2 Answers

The answer to your question is, User.create returns a User instance if it succeeds or fails. If it fails because of validations, the instance will be invalid and will have errors:

user.valid? # <= returns false
user.errors.count # <= will be > 0
user.errors.blank? # <= will be false

So your code would change from this:

if user==false or user==nil or user==vals

to this:

if !user.valid?

You can also use this pattern:

user.attributes = vals
if user.save
   ... save succeeded ...
else
   ... save failed ...
end

The save method returns a boolean true or false since you are calling it on an existing instance.

But lets get you on the right track in a few other ways:

First: you have this:

if User.exists(vals[:username])

(I'm assuming exits is a method you put on your User model because that's not a Rails thing). Instead of doing that check in your controller, you can just use another validation on the model:

class User < ActiveRecord::Base
   ...
   validates :username, unique: true
   ...
end

Now when you try to create the user, it will fail validation if you already have one with that name.

Second: You have this:

vals[:create_date] = DateTime.current

This is unnecessary. If you add a column to your model called created_at it will hold the creation date automatically (managed by ActiveRecord). You can add this, and its partner updated_at to your model in your migration like this:

create_table :users do |t|
   ...
   t.timestamps # <= tells rails to add created_at and updated_at
end

Or, since you already have a users table:

add_column :users, :created_at, :datetime
add_column :users, :updated_at, :datetime

Now you will always have the date/time of creation and last update on your user model with no need for additional code.

Third: You have this:

user = User.create(vals, :without_protection => :true)

Don't do this. Instead, change this:

vals = params[:user]

To this:

vals = params.require(:user).permit(:username, :password, :password_confirmation)

And then keep protection on:

user = User.create(vals)

You can add any additional columns you want to bring from your form to the permit() call. This is very important because it is hard to fix this kind of thing later. "If once you go down the dark path, forever will it dominate your destiny."

Fourth: You should not redirect to the user_path if the save failed, because there will be no user model to show. Instead you should re-render your new form. You also don't need flash messages for the errors. If the new form renders, it can check @user.errors and report error messages accordingly. See the ActiveRecord error object documentation.

Finally: You mention that you your validation fails even when your password is properly confirmed. I can't say for sure without seeing your form code, but make sure your password field is called password and the confirmation field is called password_confirmation. Rails looks for this *_confirmation field value specifically when validating for confirmation.

If that doesn't do it, post your form code and I'll revise.

like image 179
gwcoffey Avatar answered Sep 24 '22 10:09

gwcoffey


The answer is the ActiveRecord object. The official source code shows that create return the object if it succeeds or fails:

# File activerecord/lib/active_record/persistence.rb, line 29
def create(attributes = nil, &block)
  if attributes.is_a?(Array)
    attributes.collect { |attr| create(attr, &block) }
  else
    object = new(attributes, &block)
    object.save
    object
  end
end

How to judge it succeeds or fails

The real answer is persisted?:

if user.persisted?
  # Success
else
  # Failed
end

Why didn't use user.valid? to do it? Because sometimes it is not enough when there are some callbacks which operate other models failed:

class One < ActiveRecord::Base
  has_many :twos
  after_create :create_twos_after_create
  def create_twos_after_create
    # Use bang method in callbacks, than it will rollback while create  two failed 
    twos.create!({})    # This will fail because lack of the column `number`
  end
end

class Two < ActiveRecord::Base
  validates :number, presence: true
end

Now, we execute One.create will fail, check the log file, it will be a rollback because it failed to create two. In this case, One.create.valid? still return true, but it actually created failed, so use One.create.persisted? to replace it is necessary.

Notice: The code is tested in Rails 5.1.

like image 39
songhuang Avatar answered Sep 25 '22 10:09

songhuang