Rails 4 validates presence conditional "style" - ruby-on-rails-4

I'm trying to DRY this up a bit, but can't figure out what the syntax might be. I have two fields that I want to conditionally validate presence of: link and text. Each should require presence if the other is not #present?, but require absence if the other is #present?
This is as short as I can get it and it feels icky having to have each field broken into two parts.
validates :link,
presence: true,
unless: "text.present?",
validates_absence_of :link, if: "text.present?"
validates :text,
presence: true,
unless: "link.present?"
validates_absence_of :text, if: "link.present?"
Can anyone else help a n00b make it cleaner?
Thanks in advance.
Found Solution
I ended up going with the below solution with help from here.
validate :either_or
private
def either_or
if !(link.blank? ^ text.blank?)
errors[:base] << "Specify a link or text, but not both."
end
end

Answering my own question ;)
I ended up going with the below solution with help from here.
validate :either_or
private
def either_or
if !(link.blank? ^ text.blank?)
errors[:base] << "Specify a link or text, but not both."
end
end

Related

Custom model name in Rails 4 validation

I have a class that looks something like this:
class OrganicBipedalLifeform < ActiveRecord::Base
# Has the field 'name'
validate :presence_of_name
private
def presence_of_name
errors.add(:base, "name can't be blank") unless name.present?
end
end
And I want the validation error message to use a custom string that excludes (or modifies) the model name, say 'Human/Vulcan name can't be blank'.
If I want this to be the default message for validation errors on this model, is there a better approach than changing the flash details in every view which might display validation errors? Ie by changing something on the model itself?
Apologies if this has been answered elsewhere. I've found a lot of posts about customising the field name, but none about modifying the name of the model itself.
ETA: #TomDunning #Dan, I think I misidentified the source of the problem (or at least didn't make it sufficiently specific), so am creating a new thread to ask what I hope is a better question.
I think you can replace :base with self.class_name or self.class.table_name or a similar class method.
That is bad design, just use this:
validate :name, presence: true
"name can't be blank" would be the default error anyway.
If you then want to extract these later just call my_record.errors or similar.
For a custom error message
validate :name, presence: { message: 'must not be blank' }

Is "proc" required with conditional before_action/before_filter?

Behold, a before_filter:
class ThingController < ApplicationController
before_filter :check_stuff, :if => proc {Rails.env.production?}
end
During a recent code review, I was asked, "Is the proc is required for this to work?" The answer appears to be 'yes', but it's a reasonable question, and I had intended to answer it by referring to the Rails docs or guides or something on the use of conditionals with before_filter (now an alias of before_action).
I couldn't find any. The Action Controller Guide mentions :only/:except, but not :if/:unless.
Failing that, is there somewhere in the code I can point to that covers this? It's mentioned briefly here, but that's more about how :only and :except are handled, rather than :if or :unless.
Found it on Rails Guides: http://guides.rubyonrails.org/active_record_callbacks.html#conditional-callbacks
Turns out a Proc isn't always required for it to work.
the :if and :unless options, which can take a symbol, a string, a Proc or an Array.
So in your case you could probably get away with
before_action :check_stuff, if: "Rails.env.production?"
Finding things in Rails documentation can be a pain sometimes, but at least questions like this make things easier to find over time since StackOverflow is well indexed and has high search rankings.
From Rails 5.2 onwards, the current accepted answer is no longer be valid, and passing a string to the conditional will fail.
DEPRECATION WARNING: Passing string to :if and :unless conditional options is deprecated and will be removed in Rails 5.2 without replacement.
Going forward, a proc is now the best way to add a conditional like in the original question:
class ThingController < ApplicationController
before_action :check_stuff, :if => proc {Rails.env.production?}
end
i have done this on my code while ago. I hope that example helps to you. If you can use if statement but that should point to another method like I did here.
class Admin::ArticlesController < ApplicationController
before_filter :deny_access, :unless => :draft_and_admin?
def show
#article = Article.find(params[:id])
end
protected
def draft_and_admin?
Article.find(params[:id]).draft? && current_user.admin?
end
end
I would recommend using staby lambda. If you want know, WHY?
Please read this
class ThingController < ApplicationController
before_action :check_stuff, if: -> { Rails.env.production? }
end
which is almost equivalent to Upvote Me's answer.
Adding a method to check the if/unless conditions for a before_action should be the best way as this way you can easily accommodate any additional changes in the before_action conditions easily in future:
class ThingController < ApplicationController
before_filter :check_stuff, if: :check_stuff?
def check_stuff
end
private
def check_stuff?
Rails.env.production?
end
end

validate uniqueness in rails4 and case sensitivity fails

validates :name, uniqueness: true
The above validates name with case sensitive uniqueness. Any other default validators/options exists to include to case-insensitive checking.
Please help.
Thanks in advance.
I found this code here: https://stackoverflow.com/a/6987482/2754188
You can use this line:
validates :name, uniqueness: { case_sensitive: false }
If you're using a text-column, then the following should easily work:
validates_uniqueness_of :name
The default setting for case_sensitivity is :true and you can even add the following to your validation:
validates_uniqueness_of :name, :case_sensitive => false
This setting is however ignored by non-text columns.
If you are working on uniqueness of a record in Rails app, then please be reminded about this Rails article which says that Rails uniqueness is not fool proof. Scroll down to the bottom of this article Rails - Concurrency and integrity issues to know in detail.
In short, duplicates can still occur during concurrent operations.
I have faced these issue of duplicates in Rails app during concurrency and I had to apply a database level unique index on the table.

What is the Proper Rails way to "prefill an attribute unless it was set"?

I have a lot places where the following pattern emerges. The case is that I need to prefill an attribute with "random" information, unless it is provided by a consumer of the Model.
class Server
validates :fqdn, presence: true
before_validation prefill_fqdn, if: :must_prefill_fqdn?
private
def must_prefill_fqdn?
#what best to check against?
end
def prefill_fqdn
self.fqdn = MyRandomNameGenerator.generate
end
end
I am looking for what to check against:
nil? is rather limited and excludes values like "". It checks if it is nil, not whether it was set by a consumer.
empty? catches more, but still does not match the requirement of
"unless provided by the consumer", what if a user provides ""? It
also renders the validate presence: true pretty much useless: it will
never be invalid.
fqdn_changed? seems to match best, but its name and parent class (ActiveModel::Dirty suggests that this is not the proper test either. It is not changed but rather provided. Or is this merely semantic and is changed? the proper helper here?
So, what is the best test to see "if a consumer provided an attribute".
Providing can be either in Server.new(fqdn: 'example.com') (or
create or build. Or through one of the attribute-helpers, such as
fqdn= or update_attribute(:fqdn, 'example.com') and so on.
You could test whether the params are present like so: params[:param_name].present?
The present? method (inverse of blank?) tests for empty? but also returns false for whitespace so that '', ' ', nil, [], and {} are all false.
An improvement on this would be to use the presence method. Then you could do this directly in your controller method without the need for callbacks:
fqdn = params[:fqdn].presence || MyRandomNameGenerator.generate

Rails 4.0 Custom Validator Not Working

I am attempting to validate some dates in a Rails 4 application and it's not working.
I looked at lots of similar code samples, like this Same custom validation for several fields in Rails and this http://railscasts.com/episodes/211-validations-in-rails-3. (And others more complicated). I don't understand why my example doesn't work. Trying to find the problem, I've stripped out the actual validation code, and left a stub, because the validation doesn't seem to run and that would seem the base problem (or at least, the first problem).
Here's the code in the validator (which is in app/validators/custom_date_validator.rb
class CustomDateValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
puts "Custom validator was called"
record.errors[attribute] << "Date Not Valid" unless false
end
end
Here's the code from the model:
class CaseInformation < ActiveRecord::Base
include ApplicationHelper
validates :started, :custom_date => true
The error message is:
argument out of range
Extracted source (around line #104):
respond_to do |format|
104 if ( #case_information.update_attributes(params[:case_information]) && #case_information.update_attributes(params[:law_guardians]) )
format.html { redirect_to #case_information, notice: 'Case information was successfully updated.' }
format.json { head :no_content }
The error is (I think) intentional, I put in a date of "1/123/2012", but the validator isn't catching it. Well, actually I stripped out all the validation code and have (I think) the validator writing to the log, as it doesn't seem the validator is even running, at least, there's nothing in the log that shows it ran.
I know that the validator code is being found because in the model, if I change the validation name even a bit I get an error that the validation can't be found.
Really stupid noob question I am sure, your patience is appreciated.
Thanks.
It's indeed failing before your validator gets run-- the "argument out of range" error is what happens when you call Time.parse on a date that can't exist. In irb:
2.0.0p247 :004 > require 'time'
=> true
2.0.0p247 :005 > Time.parse("1/123/2012")
ArgumentError: argument out of range
And I'm betting that started is a datetime or timestamp attribute, right? Rails tries to convert the string from the params hash to their types before any validations are run.