Vishal Nagda Vishal Nagda - 2 months ago 4
Ruby Question

RSpec - unable to get all associate attributes in controller

Hello Programmers & Developers!!!, I'm a beginner in RoR and creating a simple project in rails to learn its working, so in that project I'm facing a problem in writing a spec for the

create
method of controller. When I'm trying to pass the associate attributes of the object in spec file, in controller it isn't get all the attributes.


In the
create
method of
subjects_controller.rb
file.
I've created a variable called
attr
in this variable I'm storing all the values sent from
subjects_controller_spec.rb
file.

attr=(params.require(:subject).permit(:name)).merge(:classroom_ids=>params[:subject][:classroom_ids],:school_ids=>params[:subject][:school_ids])


Now, If I print the value of the
attr
using
p attr
in console it's output is the exact output that I want, which is

{"name"=>"Computer", "classroom_ids"=>["1", "2"], "school_ids"=>["1"]}


But, now I'm doing
@subject = Subject.new(attr)
and printing value of
@subject
gives the following output

#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>


and after running the test I'm getting my test failed and then I printed the error
p @subject.errors
it gave me the below output

#<ActiveModel::Errors:0x007fc35444a218 @base=#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>, @messages={:school_ids=>["is not a number"], :classroom_ids=>["is not a number"]}>


So, here is my actual question is why
@subject
in
subjects_controller.rb
is not having values of
classroom_ids
and
school_ids
? If any solution or suggestion is there then please help me to sort out this problem.




Below I'm providing you all the necessary details to understand the actual problem.

Ruby Version
2.2.4


Rails Version
4.2.0


Database
MySQL


Model file subject.rb

class Subject < ActiveRecord::Base
has_and_belongs_to_many :schools
has_and_belongs_to_many :teachers
has_and_belongs_to_many :classrooms
has_and_belongs_to_many :students

validates_presence_of :name, :school_ids, :classroom_ids
validates_numericality_of :school_ids, :classroom_ids
end




Controller file subjects_controller_spec.rb


require 'rails_helper'

RSpec.describe SubjectsController, type: :controller do
before(:each) do
@school1 = FactoryGirl.create(:school)
@classroom1 = FactoryGirl.create(:classroom, :school_id=>@school1.id)
@classroom2 = FactoryGirl.create(:classroom, :school_id=>@school1.id)
@subject = FactoryGirl.build(:subject)
@subject.classrooms<<@classroom1
@subject.classrooms<<@classroom2
@subject.schools<<@school1
end
context "POST create" do
it "should be success" do
# p @subject
# p @subject.classrooms
# p @subject.classroom_ids
attributes=@subject.attributes.merge(:classroom_ids=>@subject.classroom_ids,:school_ids=>@subject.school_ids)

# In below line, I'm sending all the values to the controller to create a new subject.

post :create, :subject=>attributes

response.status.should eq 201
end
end
end




Controller file subjects_controller.rb


class SubjectsController < ApplicationController
before_action :set_subject, only: [:show, :edit, :update, :destroy]

# GET /subjects
def index
@subjects = Subject.all
end

# GET /subjects/1
def show
end

# GET /subjects/new
def new
@subject = Subject.new
end

# GET /subjects/1/edit
def edit
end



# POST /subjects
def create
attr=(params.require(:subject).permit(:name)).merge(:classroom_ids=>params[:subject][:classroom_ids],:school_ids=>params[:subject][:school_ids])

p attr ### here it prints all the values which I want to create subject.###

@subject = Subject.new(attr)

p @subject ### here is the actual problem, It's not printing all the values that need to create a new subject.###

if @subject.save
redirect_to @subject, notice: 'Subject was successfully created.', status: :created
else
p @subject.errors
render :new, status: :unprocessable_entity
end
end



# PATCH/PUT /subjects/1
def update
if @subject.update(subject_params)
redirect_to @subject, notice: 'Subject was successfully updated.', status: :ok
else
render :edit, :status => :unprocessable_entity
end
end

# DELETE /subjects/1
def destroy
@subject.destroy
redirect_to subjects_url, notice: 'Subject was successfully destroyed.'
end

private
# Use callbacks to share common setup or constraints between actions.
def set_subject
@subject = Subject.find(params[:id])
end

# Only allow a trusted parameter "white list" through.
def subject_params
params.require(:subject).permit(:name, :school_ids, :classroom_ids)
end
end




Factory file subjects.rb


FactoryGirl.define do
factory :subject do
name "Computer"
end
end




RSpec Test Report


rspec spec/controllers/subjects_controller_spec.rb
{"name"=>"Computer", "classroom_ids"=>["1", "2"], "school_ids"=>["1"]}
#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>
#<ActiveModel::Errors:0x007fcdfe8f1a28 @base=#<Subject id: nil, name: "Computer", created_at: nil, updated_at: nil>, @messages={:school_ids=>["is not a number"], :classroom_ids=>["is not a number"]}>
F

Failures:

1) SubjectsController POST create should be success
Failure/Error: response.status.should eq 201

expected: 201
got: 422

(compared using ==)
# ./spec/controllers/subjects_controller_spec.rb:21:in `block (3 levels) in <top (required)>'

Deprecation Warnings:

Using `should` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax is deprecated. Use the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }` instead. Called from /Users/vishal/project/school_system/spec/controllers/subjects_controller_spec.rb:21:in `block (3 levels) in <top (required)>'.


If you need more of the backtrace for any of these deprecations to
identify where to make the necessary changes, you can configure
`config.raise_errors_for_deprecations!`, and it will turn the
deprecation warnings into errors, giving you the full backtrace.

1 deprecation warning total

Finished in 0.40113 seconds (files took 3.03 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/subjects_controller_spec.rb:14 # SubjectsController POST create should be success

Coverage report generated for RSpec to /Users/vishal/project/school_system/coverage. 49 / 332 LOC (14.76%) covered.



For more details you can refer this Github link.


Thanks For Help In Advance.

Answer

[ "1", "2" ] is not array of integer but String! @subject has classroom_ids and school_ids, but params always treat input values as String, so validation error occurs in your Subject model. So try below to transform String to Integer:

params[:subject][:classroom_ids].map(&:to_i) 
params[:subject][:school_ids].map(&:to_i) 

How about this?

restore below without map method in the controller:

params[:subject][:classroom_ids] 
params[:subject][:school_ids]

In my PC, by modifing the Subject model as below from your github link and passed the test. Could you try this?

Class Subject < ActiveRecord::Base
  has_and_belongs_to_many :schools
  has_and_belongs_to_many :teachers
  has_and_belongs_to_many :classrooms
  has_and_belongs_to_many :students

  validates_presence_of :name, :school_ids, :classroom_ids
  validate :validate_classroom_ids
  validate :validate_school_ids

  private

  def validate_classroom_ids
    if classroom_ids.any?{ |id| !id.is_a?(Integer) }
      errors.add(:classroom_ids, 'is not a number')
      return false
    end
  end

  def validate_school_ids
    if school_ids.any?{ |id| !id.is_a?(Integer) }
      errors.add(:school_ids, 'is not a number')
      return false
    end
  end
end