Rails Complex Form Validations? - ruby-on-rails

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.

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})?(\.*)?$

What is the robust way to check form input values in Rails

I have a form and want the form input values to be check. If the values pass the check, then values can be stored in DB. I know I can check them in view, controller, or even model. I think probably the best way is to check them in all the three parts, and only check them in view before submit could cause problem because others can manually modify and send the request to the controller.
So for example, I have user variable in view. I don't want date type attribute user.start_date to be modify to be earlier than user.apply_date, how should I make the protection robust?
What you're looking for are model validations: http://guides.rubyonrails.org/active_record_validations.html
Check out the date_validator gem (https://github.com/codegram/date_validator)
It allows for validations like this:
validates :start_date, date: { after: :apply_date }
Doing it in the model, as that's the usual way. Ruby on Rails supports model errors extensively, and if there's an error you can simply redisplay the record and the errors in that record.
There are standard validations you can do to ensure a value is present, is unique, etc.
You can also do custom validations in the model
validate :start_date_cannot_be_before_apply_date
def start_date_cannot_be_before_apply_date
if start_date < apply_date
errors.add(:start_date, "can't be before the apply date")
end
end

Validate an ActiveRecord model as if it has one hypothetical change

Lets say I have some validations that I only want to run if my record is in a specific state. This allows a draft record to be saved that is incomplete, and the rest of the content can be filled in later.
validates_presence_of :intro, :codename, :body,
if: lambda { |o| o.content_state == :review }
Now I want to know if this record's content can be considered complete, which will allow it to be moved to the review state. Validations provide a fantastic framework for applying constraints and requirements to model properties, and I want to leverage that.
So I have to take a draft content record, and then validate it as if it was in the review state, and if it comes up with any errors, the content is not yet complete.
But the only way that I have managed to do this is as follows:
def content_completable?
old_content_state = content_state
self.content_state = 'review'
return valid?
ensure
self.content_state = old_content_state
end
This feels pretty kludgy to me. It seems to work, however I'm powering these states with an actual sate machine, the docs of which say manually assigned a state name is not a good thing. But I have to because there may not be a transition back.
I don't actually want to change the model at this point, even if it is valid. I only want to know if the model would be valid if it were in a different state?
I was hoping ActiveRecords #valid? method would accept a hash attributes that would override the current values on the model, but it doesn't appear to work this way.
Is there a better way to do what I'm doing?
You could clone the record, modify the state and call valid? on the cloned record.
You can set a virtual attribute and test that first:
attr_accessor :new_content_state
validates_presence_of :intro, :codename, :body,
if: lambda { |o| (o.new_content_state || o.content_state) == :review }
def content_completable?
self.new_content_state = 'review'
return valid?
end
Naturally, you may still need to cleanup after the virtual state, but that depends on how long this model persists. It's also less intrusive since you only use this attribute for a limited purpose.
A completely different approach would be to avoid using AR validators in the first place. They aren't quite designed for this purpose, and although it seems elegant at first glance, the abstraction is leaking through...

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...

Only one instance of a Model in Rails

I'm working on my first Rails app. It needs to store some information about a single advertisement that will appear on every page. The admin just needs to be able to set the URL, Title, and an image. Obviously, I only need one instance of this ad object.
I created a model that inherits from ActiveRecod::Base, but that seems like the wrong thing to do, since it is configured to save multiple ads in a database table.
What's the best way for me to do this? What should the model and controller look like?
Thanks in advance,
Avi
A better way would be adding a validation that checks if one record entry already exists.
Inside your model:
validate :check_record, on: :create #please not that validate in this case is singular
def check_record
if Ad.all.count === 1
errors[:base] << "You can only have one active advertisement"
end
end
Well, if you are going to store the information (URL, Title, image) in the database, I think having it inheriting from AR is the right thing to do. If they are going to do this through any sort of front-end, I think this is your best option.
Why not allow for multiple advertisements, but only one of them can be published at a time? That way, you can also have a history of the advertisements—I don't know how important that part is, but it might prove interesting down the line.
I would suggest that you continue using ActiveRecord but that you add a boolean attribute that determines which of the many advertisement records is the active one. I have called this field active within the following example.
You can then validate within the model that a maximum of one record is active. The validation of the active attribute should succeed under any of the following conditions
The active attribute is set to false
There are 0 records with an active value of true.
The current record already has the attribute set to true in the database.
The following class should meet your needs
class Ad < ActiveRecord::Base
named_scope :has_active, :conditions => {:active => true}
def validate
errors.add_to_base "You can only have one active advertisement"
unless self.active_flag_valid?
end
def active_flag_valid?
self.active == false ||
Ad.has_active.size == 0 ||
( Ad.has_active.size == 1 && !self.active_changed?)
end
end
I agree. If you're completely sure it's static, then it doesn't even need to be stored in the database. If it does change, Rails will give you created_at and updated_at pretty much for free, so getting the most recently created / updated is one simple way to approach displaying what's current.

Resources