Rails Tutorial - Have to save users with validations off - ruby-on-rails

I've followed the rails tutorial and have a working users system, but one thing annoys me: When saving a user, even just in the console, I have to turn off validations since it will not save without both password and password confirmation saved, and set to the same thing.
This is a pain, and seems potentially dangerous if I added other important validations to the user. So is there a good way of dealing with this? Really I want the validation to apply only when the update is coming from the user, not from my own code.
So the user model has this:
validates :password, length: { minimum: 6 }
has_secure_password # this does the hashing, comparisions, etc for us.
validates :password_confirmation, presence: true
This works great when the user is created, and for when the user is editing his/her profile since it requires that the password is entered again. But, for other edits, it's annoying. eg:
user = User.find_by_name("bob")
user.reputation = 200
user.save(validate: false) # I really don't want to have to care about password validations here.

You can call
#user.save(:validate => false)

You could try just validating on create. That will make it so the password (or other variables) is only necessary on registration but not when youre editing an existing user.

If you want to have control over which attributes to skip validations for, you may want to try using the ValidationSkipper gem:
https://github.com/npearson72/validation_skipper

Why don't you just create different models for it, a secure model for logging & password changes, and a profile model for anything else, giving out reputations, medals, and stuff.
Creating new functions in the first model allows you to totally forget about the second one, like
def reputation value
self.profile.reputation = value
self.profile.save
end

A little late to the party but if you want to update a single attribute you can use the update_attribute method which skips validation checks. In this case you could call
user.update_attribute :reputation, 200

Related

How to validate unique url with Rails and validates_url?

Use validates_url can validate url format.
Set validation in model:
validates :homepage, uniqueness: true, url: true
Can't validate these two cases as unique url:
https://stackoverflow.com
https://stackoverflow.com/
But they should be the same. If the first one is inserted into database, the second one should be validated and not allow to be inserted.
I didn't find a way to realize it using validates_url. Is it possible?
So the two validations, uniqueness and URL, happen separately, and there is nothing in the uniqueness check to handle the fact that those two URLs are essentially the same - instead, the string values are technically different, and thus it doesn't trip the uniqueness validation.
What you could do is look to tidy up your URL data before validation, with a before_validation callback in your model:
before_validation :process_url
def process_url
self.homepage = self.homepage.slice(0, self.homepage.length - 1) if self.homepage.present? && self.homepage.ends_with?("/")
end
This is called before the validations kick in, and will make sure that if the homepage attribute is present (even if you add a presence validation later if it becomes non-optional, remember this is running before validations), then any trailing / is removed.
Those two URL strings will then be the same after tidying up, and thus the second time around the validation will kick in and stop it from being saved.
Hope that helps!
I do use rubular.com to validate using regex. You can try this ^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\.*)?$

Devise gem and User model

Devise's examples and tutorials say me to have something like this in User model:
attr_accessible :name, :password, :password_confirmation, :remember_me
So I have two questions about this.
Why should I make "name" and "password" accessible? I want them to be protected, does devise let me change this?
What on earth do fields like "password_confirmation" and "remember_me" do in user model? Now I can write something like User.find(1).password_confirmation, it works but has no sense at all.
How to deal with that?
You need all four of these fields accessible in order to create the login and sign up forms. You don't have to worry about the password fields in the database, they are just virtual attributes. The actual fields in the database, which you can confirm in the schema, are actually the encrypted_password and salt. It is actually a very functional, proven and secure system. You should focus innovating in the rest of your app and let devise do its work for you, which it does very well.
I think I've found an answer.
Devise uses mass-assignment, and there is nothing you can do about that. There is a Github issue about that: https://github.com/plataformatec/devise/pull/718. I'm working on idea how we can change Devise to avoid it relying on mass-assignment. I will be happy to know your opinion.
As long as Devise takes away your right to define our attr-accessible list, there are two things you can do about that:
a. You can just use attr_readonly along with attr_accessible. It will open door for Devise, but not for your other forms, read Make attributes mass assignable only during creation
b. def mass_assignment_authorizer allows you to define dynamic attr-accessible list, read http://railscasts.com/episodes/237-dynamic-attr-accessible?view=asciicast. IMHO, this way is overkill for this kind of problem.

Which rails ActiveRecord callback to sync with service (stripe) when creating a new record and still properly use errors?

I have a User and a StripeCustomer model. Every User embeds one and accepts_nested_attributes_for StripeCustomer.
When creating a new user, I always create a corresponding StripeCustomer and if you provide either a CC or a coupon code, I create a subscription.
In my StripeCustomer:
attr_accessible :coupon_id, :stripe_card_token
What I'd like to do is, if the coupon is invalid, do:
errors.add :coupon_id, "bad coupon id"
So that normal rails controller patters like:
if #stripe_customer.save
....
else
....
end
will just work. And be able to use normal rails field_with_errors stuff for handling a bad coupon.
So the question is, at which active record callback should I call Stripe::Customer.create and save the stripe_customer_token?
I had it on before_create, because I want it done only if you are really going to persist the record. But this does strange things with valid? and worse, if you are going to create it via a User, the save of User and StripeCustomer actually succeeds even if you do errors.add in the before_create callback! I think the issue is that the save will only fail if you add errors and return false at before_validation.
That last part I'm not sure if it is a mongoid issue or not.
I could move it to before_validation :on => :create but then it would create a new Stripe::Customer even if I just called valid? which I don't want.
Anyway, I'm generically curious about what the best practices are with any model that is backed by or linked to a record on a remote service and how to handle errors.
Ok here is what I did, I split the calls to stripe into 2 callbacks, one at before_validation and one before_create (or before_update).
In the before_validation, I do whatever I can to check the uncontrolled inputs (directly from user) are valid. In the stripe case that just means the coupon code so I check with stripe that it is valid and add errors to :coupon_code as needed.
Actually creating/updating customers with stripe, I wait to do until before_create/before_update (I use two instead of just doing before_save because I handle these two cases differently). If there is an error then, I just don't handle the exception instead of trying to add to errors after validation which (a) doesn't really make any sense and (b) sort of works but fails to prevent saves on nested models (in mongoid anyway, which is very bad and strange).
This way I know by the time I get to persisting, that all the attributes are sound. Something could of course still fail but I've minimized my risk substantially. Now I can also do things like call valid? without worrying about creating records with stripe I didn't want.
In retrospect this seems pretty obvious.
I'm not sure I totally understand the scenario. you wrote:
Every User embeds one and accepts_nested_attributes_for StripeUser
Did you mean StripeCustomer?
So you have a User that has a Customer that holds the coupon info?
If so, I think it should be enough to accept nested attributed for the customer in the user, put the validation in the customer code and that's it.
See here
Let me know if I got your question wrong...

Rails Complex Form Validations?

I have a complex form for my Work model. It accepts nested attributes for Credits, Images and Videos. (it is a modified version of Eloy Duran's complex form example)
I want to validate presence of at least one Credit
I want to validate presence of at least one Image or one Video
When I do (in work.rb):
validates_presence_of :credits
it validates properly when I try to submit the form without any credits but it does not validate if I remove the last credit (by checking a check_box that adds "_delete" to credit's attributes). It submits the work deleting the one and only credit leaving the work without any credits.
Do you have any idea on how I can validate these properly?
What you need is something along the lines of:
validate :credits_present
private
def credits_present
unless credits.any?{|c| !c.marked_for_destruction? }
errors.add_to_base "You must provide at least one credit"
end
end
The conditional in credits_present may be a bit messy so let me break it down just in case you don't follow. The any? method returns true if any of the items in the Enumerable respond true to the block provided. In this case, we check that the item is not going to be deleted. So, if any of the credits are not going to be deleted we get true. If true, we're in good shape, we have at least one credit that won't be deleted. If, on the other hand, we get false we either know there aren't any credits or that any that are present will be deleted. In this case validation fails and we add the error.
Try using before_save in work.rb
before_save :check_credits
private
def check_credits
self.credits > 0
end
If credits are <= zero, the save won't go through.

Get validations of a Rails model

Given a model that has validations in the model_name.rb file, how can I access those validations manually? I'd like to cook up my own form validation system that would work alongside the built-in Rails tricks and I want to keep everything as DRY as possible. My main problem is that I need to make server-side validations before any of the form values hit the database (I'm using a multistep form).
Basically I'm wondering if there is a method like
User.validations.each do |v|
puts v.constraint.to_s + " " + v.message
end
Is there anything similar to this?
Thanks in advance.
My main problem is that I need to make
server-side validations before any of
the form values hit the database (I'm
using a multistep form).
If your model is not valid according to the validations in its class file, then its data won't get saved to the database (unless you pass false to the save method to suppress validations).
You can ask a model if it's valid at any point by invoking its valid?/invalid? methods.
The ActiveRecord object exposes the errors method after valid? is called, which gives you messages about which validations are violated. You could check valid? and then check to see if any of the fields on the part of the form you are on are invalid.
you could do something like this for a form with fields field1 and field2.
unless x.valid?
[:field1,:field2].each do |field|
yes_there_was_an_error if x.errors[field]
end
end
Your best bet is to use a state machine and store the data in the database between the various steps in the form.
You can do e.g. validates_presence_of :username, :if => proc {|u| u.signup_step >= 2 }, where signup_step is an integer column in the database.
So, even though you say you don't want to store it in the database between the forms, I think you should. If you do this, you can use regular validations and models, without nasty hacks. And honestly, I doubt doing it this way is a problem.

Resources