I am having an issue with foreign keys being permitted in my new Rails 4 application.
Lets say you have a user create form, where you can create a user and assign a user type though a dropdown.
The user model will then have a foreign key: user_type_id.
I have a RSpec test which uses FactoryGirl, and if I debug and look at params the user_type has the value 2, but if my permit params look like this:
private
def user_params
params.require(:user).permit(:name, :password, :user_type_id)
end
I won't get any user_type out. I have also tried with:
private
def user_params
params.require(:user).permit(:name, :password, user_type_attributes: [:user_type_id])
end
but without any luck.
What is the way to permit it in my post action in my User controller?
Update:
I don't have a UI yet, I try to do this the TDD way, so basically it is my RSpec-tests which fails.
The create user action looks like this:
def create
#user = User.new(user_params)
authorize #user
respond_to do |format|
if #user.save
format.html { render json: #user, status: :created, location: #user }
format.json { render json: #user, status: :created, location: #user }
else
format.html { render json: #user, status: :unprocessable_entity, location: #user }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
and the RSpec test looks like this:
it 'should create a user' do
expect {
post :create, { :user => FactoryGirl.attributes_for(:User) }
}.to change(User, :count).by(1)
end
The FactoryGirl for my user looks like this:
FactoryGirl.define do
factory :User do |f|
f.email { Faker::Internet.email }
f.password { '12345678' }
f.password_confirmation { '12345678' }
f.user_type { FactoryGirl.create(:UserType) }
end
end
If I debug my #user object it doesn't have a user_type added, but if I debug the params object, it does, contain a user_type: 2
Any ideas?
You are not creating an Id in the Factory. You are creating the new associated object. You will have to retrieve the ID in the params passed to the controller.
I suggest:
FactoryGirl.define do
factory :User do |f|
f.email { Faker::Internet.email }
f.password { '12345678' }
f.password_confirmation { '12345678' }
f.user_type 999
end
end
In your spec:
before(:each) do
type = FactoryGirl.create(:UserType, id: 999)
end
And then:
it 'should create a user' do
expect { :create, { :user => FactoryGirl.attributes_for(:User)}
}.to change(User, :count).by(1)
end
And remove the association from the FactoryGirl action.
EDIT:
If the user type is mandatory, and assuming that you have them in the database, you just need to insert in the factory the user_id you want for that user type. And you won't need to merge the params after.
Related
Tests failed after add belongs_to in Rails
I have 2 models in Rails application:
class Micropost < ApplicationRecord
belongs_to :user # Test failed after add this string
validates :content, length: { maximum: 140 }, presence: true
end
class User < ApplicationRecord
has_many :microposts
validates :name, presence: true
validates :email, presence: true
end
I added string "belongs_to :user" to model "Micropost". After that I ran tests, and they failed:
rails test
1) Failure:
MicropostsControllerTest#test_should_create_micropost [/home/kiselev/project/toy_app/test/controllers/microposts_controller_test.rb:19]:
"Micropost.count" didn't change by 1.
Expected: 3
Actual: 2
2) Failure:
MicropostsControllerTest#test_should_update_micropost [/home/kiselev/project/toy_app/test/controllers/microposts_controller_test.rb:38]:
Expected response to be a <3XX: redirect>, but was a <200: OK>
I have these 2 tests:
test "should create micropost" do
assert_difference('Micropost.count') do
post microposts_url, params: { micropost: { content: #micropost.content, user_id: #micropost.user_id } }
end
assert_redirected_to micropost_url(Micropost.last)
end
test "should update micropost" do
patch micropost_url(#micropost), params: { micropost: { content: #micropost.content, user_id: #micropost.user_id } }
assert_redirected_to micropost_url(#micropost)
end
I have a controller "MicropostsController":
class MicropostsController < ApplicationController
before_action :set_micropost, only: [:show, :edit, :update, :destroy]
# POST /microposts
# POST /microposts.json
def create
#micropost = Micropost.new(micropost_params)
respond_to do |format|
if #micropost.save
format.html { redirect_to #micropost, notice: 'Micropost was successfully created.' }
format.json { render :show, status: :created, location: #micropost }
else
format.html { render :new }
format.json { render json: #micropost.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /microposts/1
# PATCH/PUT /microposts/1.json
def update
respond_to do |format|
if #micropost.update(micropost_params)
format.html { redirect_to #micropost, notice: 'Micropost was successfully updated.' }
format.json { render :show, status: :ok, location: #micropost }
else
format.html { render :edit }
format.json { render json: #micropost.errors, status: :unprocessable_entity }
end
end
end
Setup micropost:
class MicropostsControllerTest < ActionDispatch::IntegrationTest
setup do
#micropost = microposts(:one)
end
Params in Micropost controller:
def micropost_params
params.require(:micropost).permit(:content, :user_id)
end
Fixtures Micropost:
one:
content: MyText
user_id: 1
two:
content: MyText
user_id: 1
How can I improve these tests to pass?
belongs_to method adds among others also a presence validation for the user. Somewhere in the rails code it adds something like:
validates_presence_of :user
And it checks whether the user exists. In your fixtures you have set user_id: 1. But in your tests there is no user with 1 as an ID. To fix it you have to set correct user IDs for your microposts fixtures.
You can do it in the following way. You don't have to define user_id, you can define association in the fixtures:
one:
content: MyText
user: one
two:
content: MyText
user: one
Define a user key instead of user_id and as a value use the name of the fixture from the user fixtures - in tests it would be called users(:one) if you would want to access this fixture.
Note: You can also remove the presence validation by adding required: false to your belongs_to definition but I would not recommend it.
I try testing my #UPDATE method in my Users Controller. The update of my fields (firstname, lastname, email, phone_number) works totally fine on my localhost but I can't make my test pass. Here is my code
I have this in my Users Controller :
def update
#user = User.find(params[:id])
authorize(#user)
#user.skip_reconfirmation! if #user.is_a_customer?
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to user_account_path, notice:
I18n.t('User_was_successfully_updated') }
format.json { render json: #user, status: :ok}
else
format.html { render :edit }
format.json { render json: #user.errors, status:
:unprocessable_entity }
end
end
and here is my RSPEC test :
RSpec.describe UsersController, type: :controller do
describe "PATCH update" do
let(:user) { create(:customer_user) }
before :each do
sign_in user
end
it "update the user information" do
allow(User).to receive(:find_by).and_return(user)
new_attributes = {
firstname: 'Roger',
lastname: 'Rabbit',
email: 'roger.rabbit#lapin.fr',
mobile_phone_number: '0606060606',
gender: 'female',
}
patch :update, params: { locale: I18n.locale,
id: user.id,
user: new_attributes
}
expect(user.firstname).to eq(new_attributes['firstname'])
expect(user.lastname).to eq(new_attributes['lastname'])
expect(user.email).to eq(new_attributes[:email])
expect(user.mobile_phone_number).to
eq(new_attributes[:mobile_phone_number])
end
Finally, here is the error message :
0) UsersController PATCH update user is a user update the user information
Failure/Error: expect(user.email).to eq(new_attributes[:email])
expected: "roger.rabbit#lapin.fr"
got: "customer#ecity.fr"
(compared using ==)
# ./spec/controllers/users_controller_spec.rb:29:in `block (4 levels) in <top (required)>'
Two more things :
- the test pass for firstname and lastname
- new_attributes['email'] and new_attributes['mobile_phone_number'] return nils ; that's why I symbolized key for the two last expectations
You need to reload the user object.
patch :update, ...
user.reload # ⇐ THIS
expect(user.firstname).to ...
Without reloading you have the stale object in your test and Rails has no ability to magically understand the object in the database has changed.
I'm new to Rails and I might be missing something very basic here:
User can create contact for both branches and division of a company
Branch.rb
class Branch < ApplicationRecord
belongs_to :company
has_many :contacts
end
Division.rb
class Division < ApplicationRecord
belongs_to :company
has_many :contacts
end
Contact.rb
class Contact < ApplicationRecord
belongs_to :branch
belongs_to :division
end
Now a user can create a contact from the branch page where there is no division_id and can create a contact from the Division page.
I have defined my routes.rb like this:
Routes.rb
resources :companies, :shallow => true do
get 'company_page'
resources :branches, :shallow => true do
get 'branch_page'
resources :contacts
end
resources :divisions, :shallow => true do
get 'division_page'
resources :contacts
end
end
As a result, if I create a contact from either Branch or Division, it goes to contacts#create method.
In my contacts_controller.rb, I have:
def create
#newContact = Contact.new(contact_params)
id = #division = #branch = nil
isBranch = false
if params[:branch_id] != nil
isBranch = true
id = params[:branch_id]
else
isBranch = false
id = params[:division_id]
end
if isBranch
branch = Branch.find(id)
#newContact.branch = branch
#branch = branch
else
division = Division.find(id)
#newContact.division = division
#division = division
end
respond_to do |format|
if #newContact.save
format.js
format.html { render :nothing => true, :notice => 'Contact created successfully!' }
format.json { render json: #newContact, status: :created, location: #newContact }
else
format.html { render action: "new" }
format.json { render json: #newContact, status: :unprocessable_entity }
end
end
end
But I have facing ActiveRecord Error during #newContact.save.
I'm sure I am doing something fundamentally very wrong here and Rails handles such things in another elegant way which I don't know of.
As #Anthony noted, you'll need to make your belongs_to associations optional:
# app/models/contact.rb
class Contact < ApplicationRecord
belongs_to :branch, optional: true
belongs_to :division, optional: true
end
But another problem is that params[:division_id] and params[:branch_id] are always nil. Both of those keys exist inside the [:contact] key. So the error you are getting should be ActiveRecord::RecordNotFound: Couldn't find Division with 'id'=
All that conditional logic is unnecessary. You can just make a new contact with whatever params are given. Also, you should be using Ruby convention for variable naming, which is snake_case instead of camelCase.
Finally, I assume you'll want to redirect HTML requests to either the branch or the division show page, depending on which was associated. So I've added logic to do that.
Here's a quick refactoring of the controller #create action:
def create
#new_contact = Contact.new(contact_params)
if #new_contact.save
branch = #new_contact.branch
division = #new_contact.division
redirect_path = branch ? branch_path(branch) : division_path(division)
respond_to do |format|
format.js
format.html { redirect_to redirect_path, :notice => 'Contact created successfully!' }
format.json { render json: #new_contact, status: :created, location: #new_contact }
end
else
respond_to do |format|
format.html { render action: "new" }
format.json { render json: #new_contact, status: :unprocessable_entity }
end
end
end
This proves that it works:
# spec/controllers/contacts_controller_spec.rb
require 'rails_helper'
RSpec.describe ContactsController, type: :controller do
let(:company) { Company.create!(name: 'Company Name') }
let(:division) { Division.create!(name: 'Division Name', company: company) }
let(:branch) { Branch.create!(name: 'Branch Name', company: company) }
describe '#create' do
context 'when created with a division id' do
let(:attributes) { {'division_id' => division.id, 'name' => 'Contact Name'} }
it 'creates a contact record and associates it with the division' do
expect(Contact.count).to eq(0)
post :create, params: {contact: attributes}
expect(Contact.count).to eq(1)
contact = Contact.first
expect(contact.division).to eq(division)
end
end
context 'when created with a branch id' do
let(:attributes) { {'branch_id' => branch.id, 'name' => 'Contact Name'} }
it 'creates a contact record and associates it with the branch' do
expect(Contact.count).to eq(0)
post :create, params: {contact: attributes}
expect(Contact.count).to eq(1)
contact = Contact.first
expect(contact.branch).to eq(branch)
end
end
end
end
I'm trying to do a really simple request spec for testing my API methods in my application. Right now I'm getting 302 message when I should be getting 200 with this test.
The spec:
require 'spec_helper'
describe UsersController do
describe "#create" do
it "creates a new user " do
post '/users', user: FactoryGirl.attributes_for(:user)
expect(response.status).to eq 200
end
end
end
The factory:
FactoryGirl.define do
factory :user do
sequence(:name) { |n| "Person #{n}" }
sequence(:username) { |n| "Person#{n}" }
sequence(:email) { |n| "person_#{n}#example.com"}
password "foobar"
password_confirmation "foobar"
end
end
The controller method:
def create
#user = User.new(user_params)
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: #user }
else
format.html { render :new }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
What am I missing?
So you need to make the asynchronous post request, it should be something like below,
it "creates a new user " do
xhr :post, '/users', user: FactoryGirl.attributes_for(:user)
expect(response.status).to eq 200
end
Try this, it might work
To solve the issue I need to add the ACCEPT header to my request, like so:
post "/users", { user: #user } , { accept: 'application/json' }
Doing this fixed the issue which was the server not interpreting the request as expecting json in return.
I am testing an Invoice model (a Client has many invoices, an Invoice belongs to a Client) and trying to check whether the create method works.
This is what I have come up with:
before do
#valid_invoice = FactoryGirl.create(:invoice)
#valid_client = #valid_invoice.client
end
it "creates a new Invoice" do
expect {
post :create, { invoice: #valid_client.invoices.build(valid_attributes), client_id: #valid_client.to_param }
}.to change(Invoice, :count).by(1)
end
This is my invoice factory:
FactoryGirl.define do
factory :invoice do
association :client
gross_amount 3.14
net_amount 3.14
number "MyString"
payment_on "2013-01-01"
vat_rate 0.19
end
end
This is the create method in the invoices_controller:
def create
#client = Client.find(params[:client_id])
#invoice = #client.invoices.build(params[:invoice])
respond_to do |format|
if #invoice.save
format.html { redirect_to([#invoice.client, #invoice], :notice => 'Invoice was successfully created.') }
format.json { render :json => #invoice, :status => :created, :location => [#invoice.client, #invoice] }
else
format.html { render :action => "new" }
format.json { render :json => #invoice.errors, :status => :unprocessable_entity }
end
end
end
And these are the valid attributes, ie the attributes needed for an invoice to be created successfully:
def valid_attributes
{
gross_amount: 3.14,
net_amount: 3.14,
number: "MyString",
payment_on: "2013-01-01",
vat_rate: 0.19
}
end
These are all valid. Maybe the client_id is missing?
It is only telling me that the count did not change by one - so I am not sure what the problem is. What am I doing wrong?
#gregates - Your answer was right, why did you remove it? :-) Post it again and I will check it as best answer.
This is the solution:
post :create, { invoice: valid_attributes, client_id: #valid_client.to_param }, valid_session
instead of
post :create, { invoice: #valid_client.invoices.build(valid_attributes), client_id: #valid_client.to_param }
in the test.
Also, I had to change the number in the valid_attributes. Debugging every single validation showed me that it was the same as in the factory - but must instead be unique. This solved it for me! Thanks for everyone's help!