How can I run validations on a Rails attribute I've overridden? - ruby-on-rails

I have a User model with an email attribute. Various parts of my app conceive of an "email" differently; sometimes as a string, sometimes as a hash ({ token: 'foo', host: 'bar.com' }), sometimes as an object. This is bad; I want the concept of an email to be consistent wherever I use it.
So, I use an Email object that does what I want. I don't see any good reason to create an Email table; instead, I just want to create a new Email object corresponding to an email string whenever I need one. Therefore User looks like this:
class User < ActiveRecord::Base
def email
Email.new(read_attribute :email)
end
def email= email
write_attribute :email, email.to_s
end
end
However, this causes at least two issues:
I can't search for a user by email without an explicit call to to_s.
I can't run a uniqueness validation on the email column anymore. I get a TypeError: can't cast Email to string. (I can fix this with a custom validator.)
Questions:
Is there something wrong with this approach? The fact that it breaks my validation is a code smell to me.
Is there some way to get the existing validates :email, uniqueness: { case_sensitive: false } validation to work with these new accessor definitions?

Related

Rails validate uniqueness for the ASCII approximation

I hope the title is not too unclear.
I am making arails app and I have a question about rails validation. Consider this code in the User,rb model file:
validates :name,
presence: true,
length: { maximum: 50 },
uniqueness: { case_sensitive: false }
I am using the friendly_id gem to generate slugs for the users. I wont allow users to change their name. What I now need is to ensure that Names are unique in such a way that there will be no UUID's appended to slugs if two people have the same name converted in ascii approximation.
Current behaviour is:
User 1 signs up with a name and gets a slug like this:
name: "Jaiel" slug: "jaiel"
User 2 now does the same name but a bit different:
name: "Jàìèl" slug: "jaiel-6558c3f1-e6a1-4199-a53e-4ccc565657d4"
The problem here as you see I want such a uniqueness validation that User 2 would have been rejected because both names would generate the slug "jaiel" for their friendly_id's
I would appreciate your help on that matter
Thanks
Take a look into ActiveSupport::Inflector.transliterate:
ActiveSupport::Inflector.transliterate('Ærøskøbing')
#=> "AEroskobing"
Also, with this type of validation you might want to go with custom validation (alongside the one you already have):
class User
validate :unique_slug
private
def unique_slug
names = self.class.all.map(&:asci_name)
raise ActiveRecord::RecordInvalid.new(self) if names.include?(asci_name)
end
def asci_name
ActiveSupport::Inflector.transliterate(name)
end
end
Obviously, this is super inefficient to query whole table on each validation, but this is just to point you into one of the possible directions.
Another option would be going for a callback. Transliterating the name upon creation:
before_validation: :transliterate_name
def transliterate_name
self.name = ActiveSupport::Inflector.transliterate(name)
end
It will first transliterate the name, then validate uniqueness of already transliterated name with the validation you have. Looks like a solution, definitely not as heavy as initial one.

Ruby on Rails uniqueness validation fails but record is still created

I am trying to require unique email addresses for a record in my RoR project, I have the following validation in place:
validates :email, presence: { :message => "You must provide an email address." }, uniqueness: { :message => "This email is already taken." }
validates_uniqueness_of :email, :message=>"This email is already taken"
Next to the form to add a record is a list of recently added entries. When I try to save a new record it renders the form over with an error however if you look at the list the item (with the duplicate email address) has been added. If you refresh the page or change the email in the form and resubmit the entry disappears from the list. I'm curious why it seems like the record is being saved even though the validation is firing properly.
I had thought it could be that I was creating the object with object.create(params) however when I changed that to object.build(params) it had no effect. All help is appreciated, thanks!
This is not a problem with your validation, but with the way how you render that list.
If you add your object to the list even if the validation was not successful than that element will be rendered exactly like all other elements in that list (since it has all nessessary values).
You can use the following methods to exclude such elements from a list or to handle them in a different way - grey out for example:
record.valid? # returns true if the record is valid
record.persisted? # returns true if the record exists in the database (was save)
whereas:
record.new_record? # returns true if the record wasn't saved into the database
By the way: You mix up the new and the old hash syntax in your validator definitions and the uniqueness validator is defined twice. You can change that to:
validates :email, presence: { message: 'You must provide an email address.' },
uniqueness: { message: 'This email is already taken.' }
create method creates an object and saves it to the database;
build method(Alias for: new) won't "create" a record in database, just create a new object in memory.
Validations are used to ensure that only valid data is saved into your database, validations are run before the record are sent to the database. Only Some methods will trigger validations.
create
create!
save
save!
update
update!

Devise: Unable to limit model validation to specific def's

So I'm working on the registration aspect of the site currently. I have a main sign up which is just full name, email and password. (aka new.html.erb)
After you fill in that information I direct you to a new site (setup.html.erb) and ask for more info like city, country etc.
On that you also have the edit profile account.
I am trying to make my app more secure and adding restrictions and presence etc in the model. However how can I limit them.
Currently if I do
validates :email, presence: true,
and I go to a form that doesn't even contain the email for nor permits it I get an error up that I need to add an email.
Also how do I fix this: I make presence true, I input require in html5. But still if I go to my source code and just remove the form and push submit it saves and I can bypass adding info.
Currently if I do validates :email, presence: true,
and I go to a form that doesn't even contain the email for nor permits it I get an error up that I need to add an email.
Fix:
what you need is a conditional validation. If we look at rail guides it says
Sometimes it will make sense to validate an object only when a given predicate is satisfied. You can do that by using the :if and :unless options, which can take a symbol, a string, a Proc or an Array.
So in your model you could do something like:
class Order < ActiveRecord::Base
validates :email, presence: true, if: :need_to_validate?
def need_to_validate?
#your condition to check whether you want email validation or not
end
end
Update:
You can use params[:action] and params[:controller] smartly to check in which action and controller(hence which view) you currently are in so your method would be:
def need_to_validate?
params[:action] == your_view_action && params[:controller] == your_controller_name #your condition to check whether you want email validation or not
end

Rails asking for a secret key to create model

I am creating a simple blog / news website without a user authentication system. I've decided to a use plain some sort of secret key checking technique to allow visitors who know the key to make posts.
In short, to post, you have to provide a key. Otherwise, it object should not be saved into the db.
Here's my code.
class Post < ActiveRecord::Base
attr_accessor :slaptazodis
validate :passcheck
validates :title, presence: true
validates :body, presence: true
def passcheck
if :slaptazodis != "1234"
errors.add(:base, 'Invalid pass')
end
end
end
So, I create a new model in sandbox with title, body and attribute slaptazodis set to 1234. Still, when I check errors, console keeps showing me "Invalid pass". What am I doing wrong? Is it about attributes or something? Thank you in advance :)
You should consider putting that logic away from the post model and moving it to the controller.
In the controller you could check the parameters sent by an user eg.
if params[:slaptazodis] != "1234"
If however, you're learning rails and just want to make it work with your existing solution, change from:
if :slaptazodis != "1234"
to:
if slaptazodis != "1234"
The ":" tells Rails that it should consider the characters following a symbol (almost the same as a string) just for clarification, your code is therefore almost the same as say:
if "slaptazodis" != "1234"
Which of course always renders true.

Make rails ignore inputs if user is admin

I have a user object, he can update his profile which includes name, user_name, password (blank), password_confirmation (blank), email, email_confirmation (blank), bio and picture url.
My model states that all of the (blank) MUST be filled in. but if your admin and your just going to the users page to update the user's role - You as the admin should not have to fill in user data you obviously don't know.
So how does one get around this? should I instead create a list of users with a drop down beside them? is this not, essentially , a giant form? If so - how would this get created?
essentially: What's the best way to deal with this situation?
This is currently how users get updated
def update
#user = User.friendly.find(params[:id])
#user.update_attributes(user_update_params)
if #user.save
render :show
else
render :edit
end
end
private
def user_update_params
params.require(:user).permit(:name, :user_name, :email, :email_confirmation, :password,
:password_confirmation, :bio, :picture_url, :role)
end
The real problem seems to be that you have a logical error in your User model validations.
You seem to have a validation of the form,
validates :password, presence: true, confirmation: true
which is enforced EVERY TIME, i.e. a new password has to be selected every single time a user object is saved. But this is likely not what you want. You likely want this validation to only be enforced when the user is created for the first time, i.e. when it is a new record.
You can do this with,
validates :password, presence: true, confirmation: true, if: :new_record?
update_attribute
Updates the attribute without doing validations, you need this one.
check out this api doc
EDIT:
Speaking about reading documentation
Here is an abstract from the method documentation
update_attribute(name, value) public
Updates a single attribute and saves the record. This is especially
useful for boolean flags on existing records. Also note that
Validation is skipped.
Callbacks are invoked.
updated_at/updated_on column is updated if that column is available.
Updates all the attributes that are dirty in this object.
EDIT:
If you still need to validate with this method, note that it says that callbacks are invoked, so what you can do is write your own code to validate input and use callbacks as described here.

Resources