Rails uniqueness validation failing - ruby-on-rails

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

Related

validation on uniqueness record on the association relation

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? }

Rails: get values from length validator for a field in template

I havel a model with validators on some fields. Example:
class Notation < ActiveRecord::Base
validates :name, presence: true, uniqueness: true
validates_length_of :name, minimum: 5, maximum: 128
end
is there a way to get those values from minimum and maximum for the :name field in the template (because I want to show there the value of min and max length to the user, and want to do this dynamically, with the template reflecting the values from the model)?
In the rails console, I can do something like
Notation.validators_on(:name)
which outputs
#<ActiveRecord::Validations::PresenceValidator:0x00000005428420 #attributes=[:name], #options={}>, #<ActiveRecord::Validations::UniquenessValidator:0x0000000541b8d8 #attributes=[:name], #options={:case_sensitive=>true}, #klass=Notation (call 'Notation.connection' to establish a connection)>, #<ActiveModel::Validations::LengthValidator:0x0000000540baf0 #attributes=[:name], #options={:minimum=>5, :maximum=>128}>
If I get using the array's index
Notation.validators_on(:name)[2]
I have:
#<ActiveModel::Validations::LengthValidator:0x0000000540baf0 #attributes=[:name], #options={:minimum=>5, :maximum=>128}>
but I was wondering if there is another way, maybe passing the type of validation I'm 'queryng', because I can't rely on the order of the array.
To get the validator, you can filter:
options = Notation.validators_on(:name)
.select { |v| v.is_a? LengthValidator }
.first.options
Then you can get options[:maximum] etc.
In case there is no such validator, you might have to rescue from the code, as otherwise the first will fail.

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

shoulda-matches error when using model setters before validarion

I have the Order model with the line for calculating total total price of products the user is ordering
before_validation :set_total!
validates :total, presence: true, numericality: { greater_than_or_equal_to: 0 }
set_total looks like this
def set_total!
self.total = products.map(&price).sum
end
On my specs I am trying to check if the total validations are implemented TDD
it { should validate_presence_of(:total) }
it { should validate_numericality_of(:total).is_greater_than_or_equal_to(0) }
Unfortunately I am receiving the following error
Failure/Error: it { should validate_presence_of(:total) }
Order did not properly validate that :total cannot be empty/falsy.
After setting :total to ‹nil› -- which was read back as
‹#<BigDecimal:5634b8b81008,'0.0',9(27)>› -- the matcher expected the
Order to be invalid, but it was valid instead.
As indicated in the message above, :total seems to be changing certain
values as they are set, and this could have something to do with why
this test is failing. If you've overridden the writer method for this
attribute, then you may need to change it to make this test pass, or
do something else entirely.
How can I fix this?
Using the validate_presence_of matcher is roughly equivalent to writing this test by hand:
describe Order do
it "fails validation when total is nil" do
order = Order.new
order.total = nil
order.validate
expect(order.errors[:total]).to include("can't be blank")
order.total = 42
expect(order.errors[:total]).not_to include("can't be blank")
end
end
If you were to run this test, you would find that this would fail. Why? Because in your model, you set total to a non-nil value when validations are performed. That's why you're getting this error.
So you don't really need the validation or the matcher, since neither one would fail.

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

Resources