Rails: How to Unit test for validations of nested attributes? - ruby-on-rails

I have a the following has_many model associations in my app:
User < Company < Deed < Subtransaction,
where Deed accepts_nested_attributes_for :subtransactions. I wish to test my Model validations using Minitest and fixtures.
I have trouble, however, testing the nested attributes for validity. For example , If I clear all nested attributes using
#deed.subtransactions.clear I correctly get a test response that the model is not valid.
However
#deed.subtransactions.first.num_shares = " " does not seem to work.
How do I properly test these nested attributes for validity?
My test:
class DeedTest < ActiveSupport::TestCase
def setup
#user = users(:dagobert)
#newco = companies(:newco)
params = { :deed =>
{
:date => deeds(:inc_new).date,
:subtransactions_attributes =>
{ '1' =>
{
:shareholder_name => "Shareholder 1",
:num_shares => subtransactions(:new_subt1).num_shares
},
'2' =>
{
:shareholder_name => "Shareholder 2",
:num_shares => subtransactions(:new_subt2).num_shares
}
}
}
}
#deed = #newco.deeds.new(params[:deed])
end
# test: does not raise any error messages
test "num_shares should be present for a subtransaction" do
#deed.subtransactions.first.num_shares = nil
assert_not #deed.valid?
end
# test passing: The params are submitted correctly and pass validation
test "fixture values should be valid" do
assert #deed.valid?
end
# test passing: I can test the validity of deed attributes
test "date should be present" do
#deed.date = " "
assert_not #deed.valid?
end
# test passing: when I clear #deed.subtransactions the object is no longer valid
test "a subtransaction should be present" do
#deed.subtransactions.clear
assert_not #deed.valid?
end
end
UPDATE
Deed model:
class Deed < ActiveRecord::Base
belongs_to :company
has_many :subtransactions, dependent: :destroy
accepts_nested_attributes_for :subtransactions, allow_destroy: true,
reject_if: ->(a) { a['shareholder_name'].blank? && a['num_shares'].blank? }
validates_associated :subtransactions
validates :date, presence: true
validates :subtransactions, presence: true
end
Subtransaction model:
class Subtransaction < ActiveRecord::Base
belongs_to :deed
belongs_to :shareholder
validates :num_shares, presence: true, length: { maximum: 50 },
:numericality => { only_integer: true }
validates :shareholder_name, presence: true
# end class
end

Related

Rails Create new active record with association value passed in params

I have 2 rails models which look like this
class Physician < UserProfile
has_many :state_licenses, inverse_of: :physician, autosave: true, dependent: :destroy
validates :state_licenses, :length => { :minimum => 1, message: "Please select at-least one state license"}
class StateLicense < ApplicationRecord
include RailsAdminPhysicianDependencyConcern
belongs_to :physician, inverse_of: :state_licenses
belongs_to :state, optional: true
attr_accessor :client_id
validates :state, presence: { message: I18n.t("errors.choose_one", field: 'state') }
#validates :license_number, presence: { message: I18n.t("errors.blank") }
def name
return "" unless state
"#{state.try(:name)}"
end
end
In my controller, I am using the code below to create a new Physician record with a bunch of state licenses but for some reason, the state licenses I pass to the create function never make it to the Physician model
def create
physician = nil
ActiveRecord::Base.transaction do
state_licenses = params["state_licenses"]
state_licenses_For_Association = []
if (state_licenses != nil)
state_licenses.each do |state_license|
sl = {}
sl[:state_id] = state_license
state_licenses_For_Association.push(sl)
end
end
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id, :state_licenses => state_licenses_For_Association))
user_record = nil
super do |user|
user_record = user
user.errors.delete(:user_profile)
physician.errors.messages.each { |field, messages| messages.each {|message| user.errors.add(field, message)} }
end
raise ActiveRecord::Rollback unless user_record.persisted? && physician.persisted?
end
AdminNotificationsMailer.physician_signed_up(physician).deliver_now rescue nil
end
What am I doing wrong?
Try changing this:
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id, :state_licenses => state_licenses_For_Association))
to this:
physician = Physician.create(params.permit(:first_name, :last_name, :title, :residency_board_status, :residency_specialty_id).merge(state_licenses: state_licenses_For_Association)) # note the .merge call

Rails SystemStackError: stack level too deep

I am testing my app ( Rails 5) with rspec capybara and factory girl I have the following error...
I am not sure what's happening... I am very new with rspec I hope you could help me :) thank you
Randomized with seed 41137
An error occurred in a `before(:suite)` hook.
Failure/Error: FactoryGirl.lint
SystemStackError:
stack level too deep
You will find my code below:
factories.rb
FactoryGirl.define do
factory :event do
name {Faker::Friends.character}
total_price 50
participant
end
factory :participant do
first_name { Faker::Name.first_name }
salary 900
event
end
end
event.rb
class Event < ApplicationRecord
has_many :participants, inverse_of: :event
validates :participants, presence: true
validates :name, presence: true, length: {minimum: 2}
validates :total_price, presence: true
accepts_nested_attributes_for :participants, reject_if: :all_blank, allow_destroy: true
def total_salary
all_salary = []
participants.each do |participant|
all_salary << participant.salary
end
return #total_salary = all_salary.inject(0,:+)
end
end
event_spec.rb
require 'rails_helper'
describe Event do
it { should have_many(:participants) }
it { should validate_presence_of(:participants) }
it { should validate_presence_of(:name) }
it { should validate_presence_of(:total_price) }
describe "#total_salary" do
it "should return the total salary of the participants" do
partcipant_1 = create(:participant, salary: 2000)
partcipant_2 = create(:participant, salary: 3000)
expect(partcipant_1.salary + partcipant_2.salary).to eq(5000)
end
end
end
edit
In my participant model I had to add optional: true
belongs_to :event, option: true
so fabriciofreitag suggestion works well :)
Let's take a look at your factories:
FactoryGirl.define do
factory :event do
name {Faker::Friends.character}
total_price 50
participant
end
factory :participant do
first_name { Faker::Name.first_name }
salary 900
event
end
end
In this scenario, the creation of event will create a participant, that will create an event, that will create a participant. and so on, in an infinite loop (stack level too deep).
Perhaps you could change it to something like this:
FactoryGirl.define do
factory :event do
name {Faker::Friends.character}
total_price 50
participants { create_list(:participant, 3, event: self) }
end
factory :participant do
first_name { Faker::Name.first_name }
salary 900
end
end

Rails Skip Validation For Nested Attribute if condition is true

How can I skip validation for nested_attribute if condition is true
aquarium.rb
has_many :fishes
accepts_nested_attributes_for :fishes,
fish.rb
belongs_to :aquarium
validates :ratio, :numericality => { :greater_than => 0 }, if: :skip_this_validation
then in aquariums_controller.rb
def some_action
#aquarium = Aquarium.new(aqua_params)
#aquarium.skip_this_validation = true # i know that this is not valid
#must skip validation for ratio and then save to DB
end
aquarium.rb
has_many :fishes
accepts_nested_attributes_for :fishes,
attr_accessor :skip_fishes_ratio_validation
fish.rb
belongs_to :aquarium
validates :ratio, :numericality => { :greater_than => 0 }, unless: proc { |f| f.aquarium&.skip_fishes_ratio_validation }
then in aquariums_controller.rb
def some_action
#aquarium = Aquarium.new(aqua_params)
#aquarium.skip_fishes_ratio_validation = true
#aquarium.save
end
You can just add the condition in method and check for the conditional validation
class Fish < ActiveRecord::Base
validates :ratio, :numericality => { :greater_than => 0 }, if: :custom_validation
private
def custom_validation
# some_condition_here
true
end
end
#aquarium.save(validate: false)
I believe skips validations on the model.
In Rails 5 you can simply pass optional: true to the belongs_to association.
So in your fish.rb, update association with the following code:
belongs_to :aquarium, optional: true
It will skip association validation if the fish object has no aquarium.

rspec rails nested_attributes on controller

I am using Rspec to test a controller that received nested_attributes. A class Option can has_many Suboptions.
models/suboption.rb:
class Suboption < ApplicationRecord
belongs_to :option,
optional: true
validates :name, presence: true
end
models/option.rb:
class Option < ApplicationRecord
belongs_to :activity
has_many :suboptions, dependent: :destroy
accepts_nested_attributes_for :suboptions, allow_destroy: true,
reject_if: ->(attrs) { attrs['name'].blank? }
validates :name, presence: true
end
Params:
def option_params
params.require(:option).permit(:name, :activity_id, :students_ids => [], suboptions_attributes: [:id, :name, :_destroy])
end
spec/controller/options_controller_spec.rb:
describe "POST #create" do
let(:option) { assigns(:option) }
let(:child) { create(:suboption) }
context "when valid" do
before(:each) do
post :create, params: {
option: attributes_for(
:option, name: "opt", activity_id: test_activity.id,
suboptions_attributes: [child.attributes]
)
}
end
it "should redirect to options_path" do
expect(response).to redirect_to options_path
end
it "should save the correctly the suboption" do
expect(option.suboptions).to eq [child]
end
end
Testing Post, I would like to ensure that option.suboptions to be equal to [child]. But I don't know how to pass the attributes of the instance child to suboptions_attributes. This way that I did is not working.
Found the answer:
describe "POST #create" do
let(:option) { assigns(:option) }
context "when valid" do
before(:each) do
post :create, params: {
option: attributes_for(:option, name: "opt", activity_id: test_activity.id,
suboptions_attributes: [build(:option).attributes]
)
}
end
it "should save suboptions" do
expect(option.suboptions.first).to be_persisted
expect(Option.all).to include option.suboptions.first
end
it "should have saved the same activity_id for parent and children" do
expect(option.suboptions.first.activity_id).to eq option.activity_id
end
end
This is a way of doing it.

Rspec test for validity of new object not passing

I worked through all of Michael Hartl's Ruby on Rails Tutorial with all the tests passing. Now that I'm going back and making changes to the site to suit my own needs, it's not as cut and dry as "the tests in this section aren't passing." I've created a new "Charity" object that is strongly based on Hartl's "Micropost" object. The only difference is that instead of having "content" the object has a :name, :description and :summary.
This is the code for the test that is failing, (specifically "it { should be_valid }") which is located in /charity_spec.rb:
require 'spec_helper'
describe Charity do
let(:user) { FactoryGirl.create(:user) }
before { #charity = user.charities.build(summary: "Lorem ipsum") }
subject { #charity }
it { should respond_to(:name) }
it { should respond_to(:user_id) }
it { should respond_to(:summary) }
it { should respond_to(:description) }
it { should respond_to(:user) }
its(:user) { should == user }
it { should be_valid }
...
The test actually passes at first, but once I add the validations to the charity.rb file, they return;
Failures:
1) Charity
Failure/Error: it { should be_valid }
expected valid? to return, true, got false
...
Here's the charity.rb:
class Charity < ActiveRecord::Base
attr_accessible :name, :description, :summary
belongs_to :user
validates :name, presence: true, length: { maximum: 40 }
validates :summary, presence: true
validates :description, presence: true
validates :user_id, presence: true
default_scope order: 'charities.created_at DESC'
end
I'm sure it's something stupid, but my understanding of everything is so weak I can't figure out what I'm doing wrong, my feeling is that it's something wrong with my factory, but I really don't know.
Here's my charity factory located in the factories.rb:
factory :charity do
name "Lorem ipsum"
summary "Lorem ipsum"
description "Lorem ipsum"
user
end
When I remove the :name, :summary, and :description validations from charity.rb, the test passes. For good measure, here's the beginning of my user.rb:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
has_many :charities
has_many :microposts, dependent: :destroy
Use your factory to have a proper charity:
before { #charity = user.charities.build(FactoryGirl.attributes_for(:charity)) }
It failed because you validate presence of attributes which were not set like name
If you need more background on FactoryGirl, their documentation is really good.

Resources