I have the following User model:
class User < ActiveRecord::Base
validates :email, presence:true
end
When I do:
#user = User.find_or_create_by(:email => params[:email])
I was doing #user.nil? In order to see if the user is there, but it is not acting as I thought:
puts #user.inspect
--> #<User id: nil, email: "", created_at: nil, updated_at: nil>
Why is a user gets created, even though not saved in the DB? How can I make sure that the #user is "nil"? #user.id.nil?
Thanks
The User record was not saved because it has a blank email (""), so the presence validation fails. You can check the error messages with #user.errors.full_messages.
With Active record calling .create (or one of its derivatives) will return a new record if the save operation fails (including validations). If you want an Error to be raised when the User record is invalid you can use .create!, with a bang(!). I would suggest this newer syntax which is compatible with Rails 4:
# Raises an Error if no matching record is found and the new record fails to save.
#user = User.where(:email => params[:email]).first_or_create!
# Does not raise an error if new record fails to save. The returned object will be a new record with post-validation error states.
#user = User.where(:email => params[:email]).first_or_create
Which method you choose depends on how you want your application to behave.
You can ask if it is a new record via:
#user.new_record?
Related
How to validate uniqueness in model based on if other column has value or not, not on actual content? Scope seems to compare the content of the second column. For example I have in my User model columns email and project_id (both string) among others. I want to validate email to be unique if project_id is null or has any value. Using scope allows creating objects {email: 'a#a.a', project_id: nil}, {email: 'a#a.a', project_id: '1'}, {email: 'a#a.a', project_id: '2'} and so on. I want to limit the email uniqueness so that those first two objects would be possible (project_id is nil or 1 with same email) and last object would throw error 'email has already been taken' because there's already user with same email when project_id has value. Is there proper rails way to achieve that or do I need to write some custom validation?
Of course I have better email validation also and wouldn't accept 'a#a.a', that's just an example :)
The scope option in uniqueness validation is not the same as a Rails scope. It's more related to SQL and is generally restricted to just an attribute names.
So, since you can't use custom scope in uniqueness validation, you have to write custom validation for this purpose.
validate :validate_email_on_project_id_existence
def validate_email_on_project_id_existence
if User.where(email: self.email).where(self.project_id.nil? ? 'project_id IS NULL' : 'project_id IS NOT NULL').first
errors.add(:email, "some error")
end
end
You can create your custom validation and call it with validates_with, in your case it'd be something like:
class ExampleEmailUniqueness < ActiveModel::Validator
def validate(record)
error_message = "has already been taken"
if record.project_id.nil? && Example.where(project_id: nil, email: record.email).exists?
record.errors.add :email, error_message
elsif Example.where(email: record.email).where.not(project_id: nil).exists?
record.errors.add :email, error_message
end
end
end
class Example < ApplicationRecord
validates_with ExampleEmailUniqueness
end
That validation against your scenario would yield the results:
Example.new(email: 'a#a.a').save # Saved
Example.new(email: 'a#a.a', project_id: '1').save # Saved
Example.new(email: 'a#a.a', project_id: '2').save # Email has already been taken
Example.new(email: 'a#a.a').save # Email has already been taken
Note that saving a model with a nil project_id AFTER saving one that isn't nil will still pass validation and get saved to the database, which I'm not sure if it's the intended behavior.
I read this post on
In Rails, how do I limit which attributes can be updated, without preventing them from being created?
They problem is that the u.save returns true, so it give the impression that all the values got updated. When they did not.
Is there a way to use attr-readonly, but on save return false if an attribute is read only?
class User < ActiveRecord::Base
attr_accessible :name
attr_readonly :name
end
> User.create(name: "lorem")
> u = User.first
=> #<User id: 1, name: "lorem">
> u.name = "ipsum"
=> "ipsum"
> u.save
=> true
> User.first.name
=> "lorem
You can use a validator method to write an error on changes detected to your read only field
Class User
validate :name_not_changed
private
def name_not_changed
return unless name_changed?
errors.add(:name, 'Cannot change name of User')
end
end
Is this really necessary though? If you're making your attribute read-only, why are you still leaving the possibility for it to be changed and additionally need validation errors for that operation? Ideally, you shouldn't permit the name to ever be used when updating models and the story ends there, no validation errors needed.
In my Rails app, I am trying to save MAC addresses for devices belonging to different users. Each MAC address must be unique, so I included the uniqueness validation.
The validation itself seems to be working, since duplicate records were rejected when I tried using the Rails Console (ActiveRecord::RecordNotUnique). However, my test to check that only unique records can be saved is failing.
So my questions are:
Why is my test failing and how can I fix it?
I read elsewhere that the uniqueness validation alone is not a reliable way to assure uniqueness. Should I use other methods, such as before_save callbacks?
This is the error message I'm getting for the test:
Expected #<MacAddress id: nil, user_id: nil, address: "MACADD123", created_at: nil, updated_at: nil> to be nil or false
Setup in my model files:
# app/models/mac_address.rb
class MacAddress < ApplicationRecord
validates :address, uniqueness: true
belongs_to :user
end
# app/models/user.rb
class User < ApplicationRecord
has_many :mac_addresses, dependent: :destroy
end
Test to check for uniqueness:
class MacAddressTest < ActiveSupport::TestCase
test 'mac address must be unique' do
new_mac = 'MACADD123'
assert MacAddress.create(user: User.first, address: new_mac)
assert MacAddress.all.pluck(:address).include?(new_mac)
# The 'assert_not' below is failing.
assert_not MacAddress.create(user: User.second, address: new_mac)
end
end
Thanks for any help in advance.
As per the documentation on create:
Notice there's no id for that record. It hasn't been persisted. Check for errors with .errors.full_messages to see the uniqueness validation failure.
The resulting object is returned whether the object was saved successfully to the database or not.
You should assert that it's saved, like:
mac_address = MacAddress.create(...)
assert !mac_address.new_record?
Where that tells you if it's been saved or not. Alternatively you can use create! which will raise ActiveRecord::RecordInvalid if it failed.
For future reference and for anyone viewing this question - I rewrote my test with save instead of create like below:
test 'mac address must be unique' do
test_address = 'MACADD123'
original_mac = MacAddress.new(user: User.first, address: test_address)
duplicate_mac = MacAddress.new(user: User.second, address: test_address)
assert original_mac.save
assert MacAddress.pluck(:address).include?(test_address)
assert_not duplicate_mac.save
duplicate_mac.errors.messages[:address].include?('has already been taken')
end
I have a model "User" that up until now didn't have any issues. I added some validations and noticed that I no longer could add any new Users - the record would be rolled back. So I removed the validations, but my records were still being rolled back. So I eliminated literally all the code from my model file so all it contains is this:
class User < ActiveRecord::Base
end
but I'm still getting the same error.
In my rails console:
> User.create(name: "test")
(0.6ms) BEGIN
(2.3ms) ROLLBACK
#=> #<User id: nil, name: "test", (et cetera)>
I don't even know how to start figuring out what's wrong. How can I even debug this? All my other models work normally.
This is what I added before this started:
blacklist = ['home'].freeze
validates :name, exclusion: {in: blacklist}
SOLVED:
I integrated Devise with this model, so there were some validations in place that weren't in my Devise.rb file. I had to run #user.errors to get back the errors that were preventing the record from being saved.
Try this:
user = User.new(name: "test")
user.save
user.errors # This should contain the errors that prevented your object from being saved.
I have a model Person.
One controller Api::V1::PersonsController
In my controller:
def index
#persons = Person.new(user_id: #current_user.id, type_id: params[:type_id]).method
render json: #persons, status: :ok
end
In my model:
attr_accessor :user_id, :type_id
validates_presence_of :type_id
Also tried:
validates :type_id, :presence => true
When I create my Person with no type_id, I don't get any error, what else do I need to do, or is there a better way of doing this?
From the Rails guide validation section:
The following methods trigger validations, and will save the object to
the database only if the object is valid:
create
create!
save
save!
update
update!
The bang versions (e.g. save!) raise an exception if the record is
invalid. The non-bang versions don't, save and update return false,
create just returns the object.
When you create an object using the new method, the validation rules do not fire as the object is not persisted to the database.
You can call Person.save or Person.save! after Person.new or create a Person object using create or create!. Both of these methods persist the object to the database so a validation error will be raised.
Also, in your case, you can do something like this:
Person.new(user_id: #current_user.id, type_id: params[:type_id]).valid? # => false
This way, you can check if the object is a valid object and then proceed with the rest of your code.
.new is not going to persist your Person to the database.
Validation will not be carried out unless using .save after .new or in a .create or .create! method.
Check out point 1.2 here in Rails validation guides ›