validation on uniqueness record on the association relation - ruby-on-rails

I have some query, like a :
Assignment.where(date: "2019-07-01")
.includes(crews: :truck)
.where.not({ crews: {truck_id: nil}})
.count
#=> 2 records.
Here's an example with contain:
Assignment.where(date: "2019-07-01")
.includes(crews: :truck)
.where.not({ crews: {truck_id: nil}})
.distinct.pluck('trucks.name')
#=> ["Mercedes", "BMW"]
I want to check for the uniqueness of truck_id in Assignment.where(date: "2019-07-01")
And in order to attach an existing truck_id to another Assignment object, validation was triggered.
And a message pops up that you cannot add, since such a Truck object is already attached to one of the Assignment on the day that is specified in .where
Please tell me how to implement it correctly. Thank.

If you don't want to write a validate method, you can still use uniqueness options.Use conditional option to tell which rows should do the validation.
validates :truck_id, uniqueness: true, if: Proc.new { |assignment| assignment.on_specified_day? }

Related

Which takes precedence: Rails type casting or validation?

I am very curious about what this expected behavior is for Rails 4.2 and I have not been able to find an answer to my question.
I'm adding validation to a model on all the time attributes. I want them to ONLY accept integers, not numerical strings. The data type in my schema for this attribute is an integer. I have my validation like so:
RANGE = 0..59
validates :start_minute, inclusion: { in: RANGE }, numericality: true
I've tried these other validations as well. I get the same result.
validates_numericality_of :start_minute, inclusion: { 0..59, only_integer: true }
validates :start_minute, inclusion: { in: 0..59 }, numericality: { only_integer: true }
When I pass my params to my controller from the request spec, start_minute is "12". BUT when I look at the created object, the start_minute is 12.
According to this article by ThoughtBot:
"This is because Active Record automatically type casts all input so that it matches the database schema. Depending on the type, this may be incredibly simple, or extremely complex."
Shouldn't the object not be able to be created? Is the typecasting taking precedence of my validation? Or is there something wrong with my validation? I appreciate any insight to this as I haven't been able to determine what is happening here. I've also created a model spec for this and I'm still able to create a new object with numerical strings.
Thank you for any insight you can give on this. I am still learning the magic of Rails under the hood.
From the rails docs it says,
If you set :only_integer to true, then it will use the
/\A[+-]?\d+\z/
What it(only_integer validator) does is that it validates that the format of value matches the regex above and a string value that contains only numbers like '12' is a match(returns a truthy value which is 0 and passes the validation).
2.3.1 :001 > '12' =~ /\A[+-]?\d+\z/
=> 0

Uniqueness validation rails 4

I have this uniqueness validation in my model:
validates_uniqueness_of :source_id,
scope: [:year_id],
conditions: -> { where(comment_type_id: CommentType.final.id) },
message: %(
There can be only one final comment per year
)
I have 2 types of comment, the 'CommentType.internal' which can be added to the commentary table many times, and 'CommentType.final' which can be saved once, but via update action it can be modified.
I'm able to save the one record with the final comment_type_id, but when I want to create another regardless of which commentary_type_id I have it still fails on this uniqueness validation.
What am I doing wrong?
This worked for me, I ll answer for anyone else facing the same issue :
validates_uniqueness_of :comment_type_id,
scope: [:year_id, :source_id],
if: ->(cmt) { cmt.comment_type_id == CommentType.final.id) },
message: %(
There can be only one final comment per year
)

Rails uniqueness validation failing

I have a SapEvento model which belongs to SapIntegracao model. On SapEvento, I have the following validation:
validates :params_operacao, uniqueness: true, if: :check_params
def check_params
self.sap_integracao_log.recebimento
end
I'm doing that to prevent duplicate requests on a soap integration (sometimes the same xml comes multiple times). So the params I receive are saved on that :params_operacao attribute, as a string. But the problem is the validation in saving. When I receive the XML and create the SapEvento object by doing
evento_erro = sap_integracao_log.sap_eventos.create(
evento_tipo: SapEvento.get_evento_tipo("Erro na Integração"),
params_operacao: params.to_s,
erro_descricao: (!transporte ? "Transporte não encontrado" : erro),
reenviado: false,
operacao: operacao
)
it doesn't validate and permits to create another object, even if already exists an object with same :params_operacao value.
I debugged on check_params method:
logger.debug "recebimento #{self.sap_integracao_log.recebimento}"
logger.debug "count #{SapEvento.where(params_operacao: self.params_operacao).count}"
and recebimento is true and the count is bigger than 0... so it shouldn't permitt, right?
I also tried some other syntaxes, like:
validates :params_operacao, uniqueness: true, if: "self.sap_integracao_log.recebimento"
but none of these worked. Any ideas?
EDIT:
I also tried, on console, take the last SapEvento.params_operacao and create a new SapEvento doing SapEvento.new(params_operacao: last_evento_params_operacao) and save. It also doesn't give me any errors...

How do I use the value of an attribute within a model? Ruby on Rails

Basically, I have a model, Degree, and it has three attributes: degree_type, awarded_by, and date_awarded.
There are two arrays of values that should be valid for awarded_by. The two valid values for degree_type are "one" and "two", and the valid values for awarded_by depend on "one" and "two".
If degree_type is "one" (has a value of "one", that a user would put in), I want the valid values for awarded_by to be array_one. If degree_type has a value of "two", I want the valid values for awarded_by to be array_two.
Here is the code so far:
class Degree < ActiveRecord::Base
extend School
validates :degree_type, presence: true,
inclusion: { in: ["one",
"two"],
message: "is not a valid degree type"
}
validates :awarded_by, presence: true,
inclusion: { in: Degree.schools(awarded_by_type) }
end
Degree.schools outputs an array depending on the degree type, so Degree.schools("one") would return array_one, where
array_one = ['school01', 'school02'...]
My problem is, I don't know how to access the value of degree_type within the model.
What I tried below doesn't work:
validates :awarded_by, presence: true,
inclusion: { in: Degree.schools(:degree_type) }
I tried using before_type_cast but I was either using it incorrectly or there was another problem, as I couldn't get that to work either.
When I test this I get:
An object with the method #include? or a proc, lambda or symbol is required, and must be supplied as the :in (or :within) option of the configuration hash
Help me out? :) If any more info is needed, let me know.
EDIT: To add to this, I double checked it wasn't my Degree.schools method acting up - if I go into the rails console and try Degree.schools("one") or Degree.schools("two") I do get the array I should get. :)
EDIT again: When I tried #Jordan's answer, I got errors in the cases where the awarded_by was incorrect because in those cases, valid_awarded_by_values was nil and there is no include? method for a nil object. Therefore I added an if statement checking for whether valid_awarded_by_values was nil or not (so as to return if it was), and that solved the problem!
I put this inside the method, before the unless statement and after the valid_awarded_by_values declaration:
if valid_awarded_by_values.nil?
error_msg = "is not a valid awarded_by"
errors.add(:awarded_by, error_msg)
return
end
The easiest way will be to write a custom validation method, as described in the Active Record Validations Rails Guide.
In your case, it might look something like this:
class Degree < ActiveRecord::Base
validate :validate_awarded_by_inclusion_dependent_on_degree_type
# ...
def validate_awarded_by_inclusion_dependent_on_degree_type
valid_awarded_by_values = Degree.schools(degree_type)
unless valid_awarded_by_values.include?(awarded_by)
error_msg = "must be " << valid_awarded_by_values.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')
errors.add(:awarded_by, error_msg)
end
end
end

skip validation_uniqueness for certain values

I have validation on uniqueness and I want skipping certain value or values(for example 0000):
validates_uniqueness_of :gtin, :scope => [:user_id, :item_id]
I'm tried to use next construction, but she don't work:
validates_uniqueness_of :gtin, :scope => [:user_id, :item_id], :unless => Proc.new{|base_item| base_item.gtin == '0000'}
How I can skip certain value or values?
Thanks.
P.S. update!!!
did not see a manual migration, which change behaviour
using the :unless option is certainly the right way, but i think you get the whole object as proc argument so it should be
validates_uniqueness_of :gtin, :scope => [:user_id, :item_id], :unless => Proc.new{|obj| obj.gtin == '0000'}
Not sure if this is a gotcha or not. Is the value of gtin a string or an integer? It looks like what your doing should work, but if it's an integer you would want to change to:
validates :gtin, :uniqueness => {:scope => [:user_id, :item_id]}, :unless => Proc.new{|base_item| base_item.gtin == 0000}
I'm trying to do the same thing, and I think I know what's wrong. The problem is, the if or unless "base_item" object refers to the value you're checking the uniqueness for, not the prospective match object.
Maybe you really do mean to check the item you're validating (in which case I'm barking up the wrong tree), but it seems more natural in the uniqueness case to want to exclude certain matches. For instance, I have a field is_deleted, and I want to allow a uniqueness violation if the matching object has been deleted.
I can't find any way to reference the matching object that was found in the proc. You can accomplish this by making your own a custom validation function though. For instance, if you want to validate the uniqueness of 'name', you might try something like this:
validate :full_validation
def full_validation
matches = self.class.find_all_by_name(self.name).select {|match| match.id != self.id && match.is_deleted==false}
return (matches.size>0)
end

Resources