Unable to establish relationship between two objects in Rspec - ruby-on-rails

I'm writing an RSpec called Leads controller spec. In that I'm writing a test for create action of lead controller. Now my lead controller calls Project model to create an object(Project) which also creates Contact object and assigns it to project. but when I try to test whether my Project model creating a Contact object or no, The tests are getting failed. I don't know why my contact object is not getting created:(
My leads_controller_spec.rb
describe "POST #create" do
it "should create a contact too" do
my_lead = Fabricate(:project, id: Faker::Number.number(10))
expect{
post :create, project: my_lead.attributes
}.to change(Contact, :count).by(1)
end
it "should be equal to last created contact" do
my_lead = Fabricate(:project, id: Faker::Number.number(10))
post :create, project: my_lead.attributes
expect(Project.last.contact).to eq(Contact.last)
end
end
leads_controller.rb
def create
if #lead = Project.add_new_lead(lead_params)
#lead.create_activity :create_new_lead, owner: current_user
puts "My lead in create action: #{#lead.inspect}"
else
respond_to do |format|
format.html { redirect_to :back, :alert => "Email is already Taken"}
end
end
respond_to do |format|
format.html { redirect_to leads_path }
end
end
Project.rb
def add_new_lead(inputs, data = {})
if !Contact.where(email: inputs[:email]).present?
contact = Contact.create(phone: inputs[:phone], email: inputs[:email], fullname: inputs[:fullname])
project = Project.create(name: inputs[:fullname], flat_status: inputs[:flat_status], flat_type: inputs[:flat_type], flat_area: inputs[:area], location: inputs[:locality], address: inputs[:site_address], customer_type: inputs[:customer_type])
project.contact = contact
project.save
project
else
return nil
end
end
contact_fabricator.rb
require 'faker'
Fabricator(:contact) do
email { "email_#{Kernel.rand(1..30000)}#prestotest.com" }
fullname "project#{Kernel.rand(1..30000)}"
address "address#{Kernel.rand(1..30000)}"
end
project_fabricator.rb
require 'faker'
Fabricator(:project) do
contact
end
contact.rb
field :phone, type: String
field :email, type: String
field :fullname, type: String
field :status, type: String, default: "DEFAULT"
field :address, type: String
field :new_address, type: String
field :other_data, type: Hash, default: {}
validates_presence_of :email
validates_uniqueness_of :email, :message => "Email already taken"

You are using the factories wrong in your spec. You want to use Fabricate.attributes_for(:project) instead of creating a record and taking its attributes as that will cause any uniqueness validations to fail.
require 'rails_helper'
describe ProjectsContoller
describe "POST #create" do
# don't forget to test with invalid input!
context "with invalid attributes" do
let(:attributes) { { foo: 'bar' } }
it "does not create a project" do
expect do
post :create, attributes
end.to_not change(Project, :count)
end
end
context "with valid attributes" do
let(:attributes) { Fabricate.attributes_for(:project) }
it "creates a project" do
expect do
post :create, attributes
end.to change(Project, :count).by(+1)
end
end
end
end
When it comes to the rest of your controller you should very likely be using nested attributes instead as you are not handling the case where the validation of contact fails. You're also using Contact.create when you should be using Contact.new.
Note that this is a pretty advanced topic and you might want to learn the basics first and revisit it later.
class Project < ActiveRecord::Base
belongs_to :contact
accepts_nested_attributes_for :contact
validates_associated :contact
end
class Contact < ActiveRecord::Base
has_many :projects
end
class ProjectsController < ApplicationController
def new
#project = Project.new
end
def create
#project = Project.new(project_params)
if #project.save
format.html { render :new } # don't redirect!
else
format.html { redirect_to leads_path }
end
end
private
def project_params
params.require(:project).permit(:foo, :bar, contact_attributes: [:email, :name, :stuff, :more_stuff])
end
end
<%= form_for(#project) do |f| %>
<%= f.text_field :foo %>
# These are the attributes for the contact
<%= fields_for :contact do |pf| %>
<%= pf.text_field :email %>
<% end %>
<% end %>

Related

How can i validate some attributes?

How can i validate if params have 'name' and 'section'? for example: i want to validate 'name' but if there is not then i have to return 400, same with 'section'
context 'validation' do
let!(:params) do
{ article: {
name: 'a1',
section: 'A'
...
color: 'red'
} }
end
i dont know how can i compare
it 'test, not allow empty name' do
expect(name eq '').to have_http_status(400)
end
While you could check the parameters directly:
def create
if params[:article][:name].blank? || params[:article][:section].blank?
return head 400
end
# ...
end
The Rails way of performing validation is through models:
class Article < ApplicationRecord
validates :name, :section, presence: true
end
class ArticlesController < ApplicationController
# POST /articles
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article, status: :created
else
# Yes 422 - not 400
render :new, status: :unprocessable_entity
end
end
private
def article_params
params.require(:article)
.permit(:name, :section, :color)
end
end
require 'rails_helper'
RSpec.describe "Articles API", type: :request do
describe "POST /articles" do
context "with invalid parameters" do
it "returns 422 - Unprocessable entity" do
post '/articles',
params: { article: { name: '' }}
expect(response).to have_http_status :unproccessable_entity
end
end
end
end
This encapsulates the data together with validations that act on the data and validation errors so that you display it back to the user.
Models (or form objects) can even be used when the data isn't saved in the database.

Testing comments controller. I cant pass test for create comment

I want to test comments controller, action create, but I don't know how what's wrong with it. Test are not save comment
comments_controller.rb is work in my projetc, i can see all comments by rails console (as like Comments.all. So that valid:
class CommentsController < ApplicationController
before_action :authenticate_user!, only:[:create,:vote]
before_action :show, only:[:show,:vote]
respond_to :js, :json, :html
def create
#comment = Comment.new(comment_params)
#comment.user_id = current_user.id
if #comment.save
redirect_to post_path(#comment.post.id)
else
redirect_to root_path
end
end
private
def comment_params
params.require(:comment).permit(:comment, :post_id)
end
end
comments_controller_spec.rb is here. It seems like that i send bad params
require 'rails_helper'
RSpec.describe CommentsController, type: :controller do
let(:user) {create :user}
let(:params) { {user_id: user} }
before {sign_in user}
describe '#create' do
let(:post) {create :post}
let(:params) do
{
user_id: user.id,
post_id: post.id,
comment: attributes_for(:comment)
}
end
subject {process :create, method: :post, params: params}
it 'create comment' do
expect{subject}.to change {Comment.count}.by(1)
# is_expected.to redirect_to(user_post_path(assigns(:user), assigns(:post)))
end
end
end
factory comments.rb is here:
FactoryBot.define do
factory :comment do
association :post
association :user
user_id { FFaker::Internet.email }
comment { FFaker::Lorem.sentence }
end
end

Testing mailer/ contact

I created a movie review website which allows a logged in user to add, edit and delete a movie as well as leave reviews for each movie. I have also implemented a mailer for my contact form that sends a 'fake email' (displayed on the console only).
This is my first time working with Ruby so I am unsure on how to test my controller and method for contacts. Any form of advice will be much appreciated.
contacts_controller.rb:
class ContactsController < ApplicationController
def new
#contact = Contact.new
end
def create
#contact = Contact.new(params[:contact])
#contact.request = request
if #contact.deliver
flash.now[:notice] = 'Thank you for your message. We will contact you soon!'
else
flash.now[:error] = 'Cannot send message.'
render :new
end
end
end
contact.rb:
class Contact < MailForm::Base
attribute :name, :validate => true
attribute :email, :validate => /\A([\w\.%\+\-]+)#([\w\-]+\.
attribute :message
attribute :nickname, :captcha => true
# Declare the e-mail headers. It accepts anything the mail method
# in ActionMailer accepts.
def headers
{
:subject => "My Contact Form",
:to => "your_email#example.org",
:from => %("#{name}" <#{email}>)
}
end
end
routes:
contacts GET /contacts(.:format) contacts#new
POST /contacts(.:format) contacts#create
new_contact GET /contacts/new(.:format) contacts#new
My testing so far:
require 'test_helper'
class ContactsControllerTest < ActionController::TestCase
include Devise::Test::ControllerHelpers
test "should get contact" do
get :new
assert_response :success
end
end
You can read more information here http://edgeguides.rubyonrails.org/testing.html#testing-your-mailers
require 'test_helper'
class ContactsControllerTest < ActionDispatch::IntegrationTest
test "ActionMailer is increased by 1" do
assert_difference 'ActionMailer::Base.deliveries.size', +1 do
post contacts_url, params: { name: 'jack bites', email: 'bite#jack.org', message: 'sending message', nickname: 'jackbites' }
end
end
test "Email is sent to correct address" do
post contacts_url, params: { name: 'jack bites', email: 'bite#jack.org', message: 'sending message', nickname: 'jackbites' }
invite_email = ActionMailer::Base.deliveries.last
assert_equal 'bite#jack.org', invite_email.to[0]
end
end

Getting "Couldn't find Item without an ID" when trying to render to a view.

I am new to programming, 12 weeks in to learning Ruby on Rails, and this SHOULD be an easy problem, but its not. I have a feature spec (I'm using Capybara and RSpec) I'm running on a simple "To do" list application. I want to render #item to items#show and the test keeps failing:
Failures:
1) User creates ITEM Successfully
Failure/Error: expect( page ).to have_content('washcar')
expected to find text "washcar" in "Your new ITEM was saved. Your new ITEM was saved."
# ./spec/features/user_creates_item.rb:10:in `block (2 levels) in <top (required)>'
Here is the show view:
<p><b><%= #item.body %></b></p>
Okay, simple so far. Here is the item controller:
class ItemsController < ApplicationController
def new
end
def create
#item = Item.new(item_params)
#item.save
redirect_to #item, notice: 'Your new ITEM was saved.'
end
def show
#item = Item.find(params[:id])
end
def index
end
private
def item_params
params.require(:item).permit(:body)
end
end
Here is the spec:
require 'rails_helper'
feature 'User creates ITEM' do
scenario 'Successfully' do
visit new_item_path
fill_in 'Body', with:'washcar'
click_button 'Save'
expect( page ).to have_content('Your new ITEM was saved')
expect( page ).to have_content('washcar')
end
end
Here is items#new
<h1>Items#new</h1>
<%= form_for Item.new do |form| %>
<%= form.text_field :body, placeholder: 'Body' %>
<%= form.submit 'Save' %>
<% end %>
and here is the item model:
class Item < ActiveRecord::Base
belongs_to :list
scope :unfinished, -> { where('done' => false) }
scope :unfinished_and_recent, -> { unfinished.where("created_at > ?", Time.now-7.days) }
scope :finished, -> { where('done' => true) }
validates :body, length: { minimum: 5 }, presence: true
validates :list, presence: true
end
My understanding is: on item#new, the test provides the body for the item "washcar' and form.submit 'Save' sends a request to the router, which matches the request to the ItemsController create method. There, the private method item_params passes body and item info to Item.new, which is then assigned to #item. #item.save saves the item to the db, and redirect_to #item redirects to the show method. params passes the item saved in the database to Item.find, which is then assigned to #item. items#show is then supposed to render #item.body to the view, but it looks like it doesn't. Where am I going wrong?
looks like a validation error as you have validate :list, presence: true in your model and you didn't specify list in view. Can you recheck?

Rails 4 ActiveRecord, if record exists, use id, else create a new record

When an employee is created, he is given a title. If the title is unique, the record saves normally. If the title is not unique, I want to find the existing title, and use that instead. I can't figure out how to do this in the create action.
employer.rb
class Employee < ActiveRecord::Base
belongs_to :title, :class_name => :EmployeeTitle, :foreign_key => "employee_title_id"
accepts_nested_attributes_for :title
end
employer_title.rb
class EmployerTitle < ActiveRecord::Base
has_many :employees
validates :name, presence: true, length: { maximum: 50 },
uniqueness: { case_sensitive: true }
end
new.html.erb
<%= f.simple_fields_for :title do |title| %>
<%= title.input :name, label: "Title" %>
<% end %>
employees_controller.rb
def create
if EmployeeTitle.exists?(name: employee_params[:title_attributes][:name])
# find title and use it?
else
#employee = current_user.employee.build(employee_params)
end
if #employee.save
flash[:success] = "Employee #{#employee.title.name} created."
redirect_to #employee
else
render 'new'
end
end
Edit: Using first_or_create
def create
EmployeeTitle.where(name: employee_params[:title_attributes][:name]).first_or_create do |title|
#employee = current_user.employees.build(employee_params, :title => title)
end
if #employee.save
flash[:success] = "Employee #{#employee.title.name} created."
redirect_to #employee
else
render 'new'
end
end
This makes the #employee go out of scope. Error: Undefined method `save' for nil:NilClass.
In addition, if I do this, won't the title be created regardless of whether the rest of the employee data is valid?
Using private method
employee.rb
private
def title_attributes=(attributes)
self.title = EmployeeTitle.find_or_create_by_name(name: attributes[:name])
end
The value is not being set. I get a "cannot be blank" validation error. The parameters include
employee: !ruby/hash:ActionController::Parameters
title: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
name: Consultant
The !ruby/hash:ActiveSupport::HashWithIndifferentAccess was not there before.
employee_params
private
def employee_params
params.require(:employee).permit(
title_attributes: [:id, :name],
)
end
What you need to do is to change this:
def create
if EmployeeTitle.exists?(name: employee_params[:title_attributes][:name])
# find title and use it?
else
#employee = current_user.employee.build(employee_params)
end
if #employee.save
flash[:success] = "Employee #{#employee.title.name} created."
redirect_to #employee
else
render 'new'
end
end
with this:
def create
#employee = current_user.employee.build(employee_params)
if #employee.save
flash[:success] = "Employee #{#employee.title.name} created."
redirect_to #employee
else
render 'new'
end
end
Now, override title_attributes method by putting this code in your app/models/employee.rb file:
def title_attributes=(attributes)
self.title = EmployeeTitle.find_or_create_by_name(attributes[:name])
end
Now, every time you'll create an employee whose name already exists with the particular name, it'll be used by default for associating it as title. Let the controller be skinny as it used to be.
Read more about find_or_create_by method here.
However, your question's title says: Rails 4, but you have tagged ruby-on-rails-3.2. If you're using Rails 4 then you can use this instead:
EmployeeTitle.find_or_create_by(name: attributes[:name])

Resources