I need to unit test a promotions model where every campaign has a URL. There is a polymorphic association between promotions and reference_link. the reference link fixture:
reference_link:
linkable:
fix_1 (PromoCode)
How do I convince rails that the promotions fixture does indeed have a URL belonging to it?
In promotions test helper:
test "should have a URL associated with the promo code" do
promo_code = promo_codes(:fix_1)
promo_code.reference_link.url = nil
assert_not promo_code.valid?
promo_code2 = promo_codes(:fix_2)
assert promo_code2.valid?
end
promocode.rb
class PromoCode < ActiveRecord::Base
belongs_to :reward
has_one :reference_link, as: :linkable, dependent: :destroy
validates :email, presence: true
validates :code, presence: true
end
reference_link.rb
class ReferenceLink < ActiveRecord::Base
belongs_to :linkable, polymorphic: true,touch: true
validates :link_name, presence: true
validates :link_url, presence: true
validates_format_of :link_url, :with => /\A#{URI::regexp(['http', 'https'])}\z/
validates_length_of :link_url,:link_name, :maximum => 255
end
It's simple, you almost got it right. First, ensure the fixtures are correctly set:
# promo_codes.yml
promo_1:
email: 'foobar#gmail.com'
name: 'Foobar'
# reference_links.yml
reference_1:
linkable: promo_1
link_name: 'lorem'
link_url: 'http://loremipsum.com'
The tests:
# promotion_test.rb
test "should have a URL associated with the promo code" do
promo_code = promo_codes(:promo_1)
assert_eq true, promo_code.reference_link.present?
end
Do not forget that file naming is an important part of convention that Rails use.
Related
I'm new to rails, I have a trip class with three foreign key. Two of these associate it with the same class: Place.
This is my model:
class Trip < ApplicationRecord
belongs_to :from, class_name: "Place", foreign_key: "from_id"
belongs_to :to, class_name: "Place", foreign_key: "to_id"
belongs_to :vehicle, class_name: "Vehicle", foreign_key: "vehicle_id"
validates :price, presence: true
validates :time, presence: true
validates :from_id, presence: true
validates :to_id, presence: true, if: :from_different_to?
def from_different_to?
to_id != from_id
end
end
All model tests pass except for the last one:
class TripTest < ActiveSupport::TestCase
def setup
#place1 = Place.create(name:"NewYork",cap:"11111",lat:"1234",long:"1478")
#place2 = Place.create(name:"Los Angeles", cap:"22222", lat:"1234",long:"1478")
#vehicle = Vehicle.create(targa: "ab123cd",modello:"500",marca:"Fiat", posti:5,alimentazione:"benzina")
#trip = Trip.new(price: 10, time: Time.new(2021, 10, 14, 12,03), from_id: #place1.id, to_id: #place2.id,vehicle_id: #vehicle.id)
end
...
test "Departure id and arrival id should be different" do
#trip.to_id = #place1.id
assert_not #trip.valid?
end
that result in a failure:
Failure:
TripTest#test_Departure_id_and_arrival_id_should_be_different [/media/alessandro/DATA/Universita/Magistrale/1_anno/Programmazione_concorrente/hitchhiker/test/models/trip_test.rb:45]:
Expected true to be nil or false
I'm not able to understand why.
Can someone help me?
It seems like you think validates ... if: works differently as it actually does. This line
validates :to_id, presence: true, if: :from_different_to?
translates to validate that the to_id is present if the from_different_to method returns true. When from_different_to evaluates to false then do not validate. See Rails Guides.
That means when you define
#trip.to_id = #place1.id
assert_not #trip.valid?
in your test then the first line disables the check for the presence of the to_id. No validation, no error...
I suppose what you really try to achieve is to validate that to to_id is present and from_id and to_id are not equal. This can be done with a custom validation like this:
validates :to_id, presence: true
validate :validates_places_are_different
private
def validates_places_are_different
errors.add(:to_id, "must be different to from_id") if to_id == from_id
end
I'm not able to understand why. Can someone help me?
That if conditionally enables a validation. Your to_id is the same as from_id and so to_id is not validated at all. But even if it was, to_id has a value, so there wouldn't be an error from this field.
Overall, I'm not quite sure why are you expecting a validation error here or what that error should be. In my experience, assertions like assert_not #model.valid? are virtually useless. The record might not be valid because of unrelated reasons and you'll have no idea. Personally, I assert the exact error message I'm expecting. Something along these lines (rspec syntax)
it "requires first_name" do
expected_messages = {
first_name: [:blank],
}
#model.valid?
expect(#model.errors.full_messages).to eq expected_messages
end
An alternative to that of #spickermann is that:
class Trip < ApplicationRecord
belongs_to :from, class_name: "Place", foreign_key: "from_id"
belongs_to :to, class_name: "Place", foreign_key: "to_id"
belongs_to :vehicle, class_name: "Vehicle", foreign_key: "vehicle_id"
validates :price, presence: true
validates :time, presence: true
validates :from_id, presence: true
validates :to_id, numericality: {other_than: :from_id}, if: :from_place_id?
def from_place_id
from_id
end
def from_place_id?
!from_id.nil?
end
end
Note that we have to put a control to execute the last validates only if from_id is not null, because if we doesn't do that, we vanificate the control validates :from_id, presence:true on the superior line.
I am writing some unit tests in Ruby on Rails. Even when I expect the test to have an outcome that passes the validation, the test fails.
Here is my model that I am testing:
class Post < ApplicationRecord
def permitted_params
params.require(:post).permit(:title, :body, :category_id, :admin_user_id)
end
belongs_to :category
belongs_to :admin_user
has_many :comment, :dependent => :destroy
validates :title, presence: false
validates_length_of :body, :minimum => 0
end
Here are my tests for this model:
class PostTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
test "If title is there" do
post = Post.create(:title => "", :body => "")
assert post.valid?
end
test "If body is long enough" do
post = Post.create(:title => "Some Title", :body => "hhhhhhhhhhhhhhhhhhhhhhhhh")
assert post.valid?
end
end
This is the error message:
F
Failure:
PostTest#test_If_title_is_there [test/models/post_test.rb:9]:
Expected false to be truthy.
I get two failures after running my test. I am using Ruby on Rails version 5.2.1 and Ruby version 2.5.1. Any help would be much appreciated. I am not quite sure why both tests are failing. Thank you in advance.
There is no point in having validates :title, presence: false
Most probably you want to validate the presence of title, so you should have presence: true.
In addition, I don't see any sense for validating length, against zero.
Also, I'd edit your code in the following way, since there is no point in having strong_parameters in the model.
class Post < ApplicationRecord
belongs_to :category
belongs_to :admin_user
has_many :comment, dependent: :destroy
validates :title, presence: false
validates :body, length: { minimum: 0 }
end
And finally, you can use https://github.com/thoughtbot/shoulda-matchers for more convenient validations testing.
To answer your question on why both tests are failing, the reason I believe is with validates :title, presence: false like others have pointed out and I don't think it's needed here.
1) I believe the first one fails because you are setting the title to an empty string. I could be wrong, but try setting it to nil and see if it still fails.
2) Similar to the first test, you are setting a title while your code expects it to not be present, causing the validation to also fail.
Hope this helped clearing some things up for you.
I have a mode named Exam.
There are some columns in exames:
:title
:subject_id
:exam_type
I want to know how to implement this:
class Exam < ApplicationRecord
validates :title, presence: true
validates :subject_id, presence: true, if: :no_exam_type?
def no_exam_type?
self.exam_type == ""
end
end
That is to say, I want to create a exam:
Exam.create(title: "first exam", exam_type: "something")
The subject_id must be exist, when exam_type is blank, such as exam_type="" or just do:
Exam.create(title: "first exam", subject_id: 3)
because exam_type has a default blank value.
But the subject_id doesn't necessary provide, when exam_type not blank, such as exam_type="something".
Exam.create(title: "first exam", exam_type: "something", subject_id: 3)
I test it, but no lucky.
How to do that? Thanks appreciate.
In Rails 5 belongs_to associations default to optional: false. Which means that the model will automatically validate the presence of the association.
class Thing < ApplicationRecord
belongs_to :other_thing
end
Thing.create!
# => ActiveRecord::RecordInvalid: Validation failed: other_thing can't be blank
So you need to set the association as optional and make sure the column is nullable.
class Exam < ApplicationRecord
belongs_to :subject, optional: true
validates :title, presence: true
validates :subject_id, presence: true, if: :no_exam_type?
def no_exam_type?
!self.exam_type.present?
end
end
Have you tried like this.
validates :subject_id, presence: true, :if => exam_type.blank?
you can refer the doc here to suite your requirement
use validates_presence_of instead.
validates_presence_of :subject_id, if: :no_exam_type?
def no_exam_type?
self.exam_type.nil?
end
I am relatively new to rails and I can't figure out how to fix this bug.
So here are my models:
User
class User < ActiveRecord::Base
before_save { email.downcase! }
has_many :tickets
has_many :events, through: :tickets
validates :first_name, presence: true
validates :last_name, presence: true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255},
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
end
Event:
class Event < ActiveRecord::Base
has_many :tickets
has_many :users, through: :tickets
validates :event_title, presence: true
validates :event_place, presence: true
validates :event_description, presence: true
validates :event_date, presence: true
validates_numericality_of :event_number_tickets, presence: true, numericality: { only_integer: true }, greater_than: 0
end
Ticket:
class Ticket < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
I have a test file for tickets which is
require 'test_helper'
class TicketTest < ActiveSupport::TestCase
def setup
#user = User.new(first_name: "Example", last_name: "User")
#event = Event.new(event_title: "Example Event",
event_place: "123 Example Ave.",
event_description: "Our example event for children to get introduced to technology",
event_date: DateTime.now,
event_number_tickets: 30)
#ticket = Ticket.new(event: #event, ticket_type: "regular", number_of_children: 1)
#user.tickets << #ticket
end
test "should be valid" do
assert #user.tickets.valid?
end
end
Whenever I run this test I get the following error:
NoMethodError: undefined method `valid?' for #<Ticket::ActiveRecord_Associations_CollectionProxy:0x007fae767617c8>
test/models/ticket_test.rb:18:in `block in <class:TicketTest>'
Do I have to create a custom valid method for this particular example.
There's not a valid? method for an ActiveRecord collection. If you're trying to test that the last Ticket you created is valid, you can do something like assert #ticket.valid?.
I think you might be doing a bit too much work in your setup method. You could try to just setup your User and Event model in the setup, and break the rest of what you described into different test blocks.
I have a nested form called Transaction that includes a child object called Banking Information Form. The banking information validations should not be triggered when the parent's (transaction) transaction_mechanism field is set to "cheque". For some reason when I set the transaction_mechanism to "cheque" in the form, it's still calling the banking information form validations. In the bank_information_form model I have a method called cheque_transaction? that should prevent the validations from triggering if the transaction_mechanism is "cheque", but I'm receiving an error.
The error
no block given (yield)
transaction.rb
class Transaction < ActiveRecord::Base
# Require packages
require 'time'
# Associations
belongs_to :user
has_one :banking_information_form
# Nested attributes
accepts_nested_attributes_for :banking_information_form
# Validations
validates_associated :banking_information_form
validate :check_debit_or_credit
validates :transaction_mechanism, :transaction_kind, :debit, :salesforce_id, presence: true
validate :debit, :credit,numericality: { only_integer: true }
end
banking_information_form.rb
class BankingInformationForm < ActiveRecord::Base
# Associations
# Belongs to :transaction
belongs_to :owner, foreign_key: "transaction_id", class_name: "Transaction"
# Validations
validates :financial_institution, :transit_number, :bank_account_number, presence: true, :unless => :cheque_transaction?
validates :transit_number, length: {is: 5,
too_short: "Your transit number is 5 digits long.",
too_long: "Your transit number is 5 digits long."}
validates :bank_account_number, length: {minimum: 4, maximum: 12,
too_short: "Your bank account number will be between 4 and 12 digits long.",
too_long: "Your bank account number will be between 4 and 12 digits long."}
private
def cheque_transaction?
self.transaction.transaction_mechanism == "Cheque"
end
end
class Transaction < ActiveRecord::Base
has_one :banking_information_form
# Other code
end
As you described, your banking informations will be validated only when the parent(transcactions) mechanism is not 'Cheque' :
class BankingInformationForm < ActiveRecord::Base
belongs_to :transaction #this was missing in your code.
validates :financial_institution, :transit_number,
:bank_account_number, presence: true, unless: :cheque_transaction?
# other validations here
private
def cheque_transaction?
transaction.transaction_mechanism == "Cheque"
end
end
After some more trial and error- I found a way to get my conditional validation to work. I first changed the model name from 'Transaction' to 'AccountTransaction'. I did this to comply with the conventions of rails after I found out 'Transaction' is a reserved word in rails.
Once I changed the model name, I appended my association with inverse_of: :account_transaction. More can be read about inverse_of here.
account_transaction.rb
class AccountTransaction < ActiveRecord::Base
# Require packages
require 'time'
# Associations
belongs_to :user
has_one :banking_information_form, inverse_of: :account_transaction
# Nested attributes
accepts_nested_attributes_for :banking_information_form
# Validations
validates_associated :banking_information_form
validate :check_debit_or_credit
validates :transaction_mechanism, :transaction_kind, :debit, :salesforce_id, presence: true
validate :debit, :credit,numericality: { only_integer: true }
end
banking_information_form.rb
class BankingInformationForm < ActiveRecord::Base
# Associations
belongs_to :account_transaction
# Validations
validates :financial_institution, :transit_number, :bank_account_number, presence: true, :unless => :cheque_transaction?
validates :transit_number, length: {is: 5,
too_short: "Your transit number is 5 digits long.",
too_long: "Your transit number is 5 digits long."}, :unless => :cheque_transaction?
validates :bank_account_number, length: {minimum: 4, maximum: 12,
too_short: "Your bank account number will be between 4 and 12 digits long.",
too_long: "Your bank account number will be between 4 and 12 digits long."}, :unless => :cheque_transaction?
private
def cheque_transaction?
self.account_transaction.transaction_mechanism == "Cheque"
end
end