CRUD method for a model that belongs_to many models Rails - ruby-on-rails

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

Related

Tests failed after add 'belongs_to' to model in Rails application

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.

Controller test: "ShoppingList.count" didn't change by 1. Expected: 3 Actual: 2

I test the controlller "shopping_list". However when I start the test I get this error:
Failure:
ShoppingListsControllerTest#test_should_create_shopping_list [C:/Users/Clemens/meindorfladen/Server/test/controllers/shopping_lists_controller_test.rb:30]:
"ShoppingList.count" didn't change by 1.
Expected: 3
Actual: 2
So one parameter is missing, but how can this be? Does somebody know the answer? Here is the code:
shopping_lists.yml
shopping_list_drogerie:
user: user_heiko
name: Drogerie
created_at: <%= Time.now %>
updated_at: <%= Time.now %>
shopping_list_lebensmittel:
user: user_schmitt
name: Lebensmittel
created_at: <%= Time.now %>
updated_at: <%= Time.now %>
db/schema
create_table "shopping_lists", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t|
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
t.index ["user_id"], name: "index_shopping_lists_on_user_id", using: :btree
end
models/shopping_list.rb
class ShoppingList < ApplicationRecord
# db associations
belongs_to :user
# if a shopping list is deleted, also delete information about all items on the list
has_many :list_items, :dependent => :destroy
# if a shopping list is deleted, also delete information about who it was shared with
has_many :shared_lists , :dependent => :destroy
has_many :shared_with_users,through: :shared_lists, :source => :user
has_many :invitation
has_one :appointment
# validations
validates :user, :presence => true
validates :name, presence: true, allow_blank: false, uniqueness: {scope: :user_id}
end
controllers/shopping_lists_controller.rb
class ShoppingListsController < ApplicationController
load_and_authorize_resource
# GET /shopping_lists/1
# GET /shopping_lists/1.json
def show
end
# POST /shopping_lists
# POST /shopping_lists.json
def create
respond_to do |format|
if #shopping_list.save
format.html { redirect_to shopping_list_list_items_path(#shopping_list), alert: 'Shopping list was successfully created.' }
format.json { render :show, status: :created, location: #shopping_list }
else
format.html { render :new }
format.json { render json: #shopping_list.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /shopping_lists/1
# PATCH/PUT /shopping_lists/1.json
def update
respond_to do |format|
if #shopping_list.update(shopping_list_params)
format.html { redirect_to #shopping_list, notice: 'Shopping list was successfully updated.' }
format.json { render :show, status: :ok, location: #shopping_list }
else
format.html { render :edit }
format.json { render json: #shopping_list.errors, status: :unprocessable_entity }
end
end
end
# DELETE /shopping_lists/1
# DELETE /shopping_lists/1.json
def destroy
#shopping_list.destroy
respond_to do |format|
format.html { redirect_to shopping_lists_url, notice: 'Shopping list was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_shopping_list
#shopping_list = ShoppingList.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
private def shopping_list_params
params.require(:shopping_list).permit(:name)
end
end
EDIT: sorry I forgot the test-controller: shopping_lists_controller_test
require 'test_helper'
class ShoppingListsControllerTest < ActionDispatch::IntegrationTest
include Devise::Test::IntegrationHelpers
include Warden::Test::Helpers
setup do
#drogerieShoppingList = shopping_lists(:shopping_list_drogerie)
#heiko = users(:user_heiko)
#heikoAppointment = appointments(:appointment_heiko)
end
test "should get index" do
login_as(#heiko)
#heiko.confirmed_at = Time.now
get shopping_lists_url
assert_response :success
end
test "should get new" do
login_as(#heiko)
#heiko.confirmed_at = Time.now
get new_shopping_list_url
assert_response :success
end
test "should create shopping_list" do
login_as(#heiko)
#heiko.confirmed_at = Time.now
assert_difference('ShoppingList.count') do
#post shopping_lists_url, params: { shopping_list: #drogerieShoppingList.attributes, user_id: #heiko.id, appointment: #heikoAppointment }
post shopping_lists_url, params: { shopping_list: #drogerieShoppingList.attributes }
end
assert_redirected_to shopping_list_url(ShoppingList.last)
end
test "should show shopping_list" do
login_as(#heiko)
#heiko.confirmed_at = Time.now
get shopping_list_url(#drogerieShoppingList)
assert_response :success
end
test "should get edit" do
login_as(#heiko)
#heiko.confirmed_at = Time.now
get edit_shopping_list_url(#drogerieShoppingList)
assert_response :success
end
test "should update shopping_list" do
login_as(#heiko)
#heiko.confirmed_at = Time.now
patch shopping_list_url(#drogerieShoppingList), params: { shopping_list: {name: 'WochenendEinkauf' } }
assert_redirected_to shopping_list_url(#drogerieShoppingList)
end
test "should destroy shopping_list" do
login_as(#heiko)
#heiko.confirmed_at = Time.now
assert_difference('ShoppingList.count', -1) do
delete shopping_list_url(#drogerieShoppingList)
end
assert_redirected_to shopping_lists_url
end
end
The problem isn't about the number of parameters.
Add the controller test code, because there's a test case that fails.
But when I look closer to your controller, the create method didn't actually created a #schopping_list. It just tries to save a #shopping_list variable which can easily be a nil.
UPDATE:
There seems to be two issues:
#create action in controller
The #sopping_list isn't initialized at all, You have to build the #shopping_list model before saving it. Like this:
def create
#shopping_list = SoppingList.new(shopping_list_params)
respond_to do |format|
if #shopping_list.save
...
Don't know whether the name field is the only field required to create a SoppingList model. As I see you've permitted only that one:
params.require(:shopping_list).permit(:name)

Create action in polymorphic association not working

I'm having trouble with the create action in my plannings_controller.
def new
#plannable = find_plannable
#planning = #plannable.plannings.build
3.times { #planning.periods.build }
respond_to do |format|
format.html # new.html.erb
format.json { render json: #planning }
end
end
def create
#plannable = find_plannable
#planning = #plannable.plannings.build(params[:planning])
respond_to do |format|
if #planning.save
format.html { redirect_to #plannable }
format.json { render json: #planning, status: :created, location: #plannable }
else
format.html { render action: "new" }
format.json { render json: #planning.errors, status: :unprocessable_entity }
end
end
end
def find_plannable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
In the #new action, the find_plannable method returns the value I want, but in the #create action it returns nil and I have no idea why this is happening.
My models are just like Ryan Bates' in the rails cast polymorphic episode:
#PLANNING MODEL
class Planning < ActiveRecord::Base
attr_accessible :subsubsystem_id, :subsystem_id, :system_id, :plannable_type, :plannable_id, :periods_attributes, :_destroy
has_many :periods, :dependent => :destroy
belongs_to :plannable, polymorphic: true
accepts_nested_attributes_for :periods, :reject_if => lambda { |a| a[:planned_quantity].blank? }, :allow_destroy => true
end
#SUBSUBSYSTEM MODEL
class Subsubsystem < ActiveRecord::Base
attr_accessible :hh, :name, :percentage, :price, :subsystem_id, :total_quantity, :unity, :value, :weight
belongs_to :subsystem
has_many :plannings, :as => :plannable
end
Can anyone help me?! thanks in advance!
edit: parameters:
{"utf8"=>"✓",
"authenticity_token"=>"vSr7C1+3+RhYArAmYz+zuAsLXsXriwouF771bn79+Is=",
"planning"=>{"periods_attributes"=>{"0"=>{"planned_quantity"=>"11"},
"1"=>{"planned_quantity"=>"6"},
"2"=>{"planned_quantity"=>"8"}},
"_destroy"=>"0"},
"commit"=>"OK"}
In the POST parameters, there isn't such a field matching /(.+)_id$/, so, your attempt to find the class will fail inside find_plannable
The simple fix is, in #new, add a hidden field of the plannable_id inside the form. You've already got #plannable there, so that's easy.
Then you'll have plannable_id and its value to feed find_plannable.
Have you checked your params it seems that you are having different value for params in case of create and new. Can you please post params hash for both create and new, it may help others is solving your problem

Rspec nested controller create method does not increase count by 1

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!

Rails: model accepts nested attributes but controller doesn't seem to care

I'm struggling to get nested attributes down. Working off of Railscast 196, I tried to setup my own app that does basic nesting. Users can create scavenger hunts. Each hunt consists of a series of tasks (that can belong to any hunt, not just one). I got a little help here and tried to learn from a post with a similar issue, but I'm still stuck. I've been hacking around for hours and I've hit a brick wall.
class HuntsController < ApplicationController
def index
#title = "All Hunts"
#hunts = Hunt.paginate(:page => params[:page])
end
def show
#hunt = Hunt.find(params[:id])
#title = #hunt.name
#tasks = #hunst.tasks.paginate(:page => params[:page])
end
def new
if current_user?(nil) then
redirect_to signin_path
else
#hunt = Hunt.new
#title = "New Hunt"
3.times do
#hunt = #hunt.tasks.build
#hunt = #hunt.hunt_tasks.build
hunt = #hunt.hunt_tasks.build.build_task
end
end
end
def create
#hunt = Hunt.new(params[:hunt])
if #hunt.save
flash[:success] = "Hunt created!"
redirect_to hunts_path
else
#title = "New Hunt"
render 'new'
end
end
....
end
With this code, when I try and create a new hunt, I'm told that there's no method "build_task" (it's undefined). So when I remove that line and use the second bit of code that's commented out above, I get the error below.
NoMethodError in Hunts#new
Showing /Users/bendowney/Sites/MyChi/app/views/shared/_error_messages.html.erb where line #1 raised:
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.errors
Extracted source (around line #1):
1: <% if object.errors.any? %>
2: <div id="error_explanation">
3: <h2><%= pluralize(object.errors.count, "error") %>
4: prohibited this <%= object.class.to_s.underscore.humanize.downcase %>
Trace of template inclusion: app/views/tasks/_fields.html.erb, app/views/hunts/_fields.html.erb, app/views/hunts/new.html.erb
And when I use the first bit of code that's commented out in the hunt controller, then I get an error telling me that my 'new' method has an unintialized constant:
NameError in HuntsController#new
uninitialized constant Hunt::Tasks
I'm at my wit's end. Any suggestions on what exactly I'm doing wrong? Or a strategy Here are my models:
class Hunt < ActiveRecord::Base
has_many :hunt_tasks
has_many :tasks, :through => :hunt_tasks #, :foreign_key => :hunt_id
attr_accessible :name
validates :name, :presence => true,
:length => { :maximum => 50 } ,
:uniqueness => { :case_sensitive => false }
end
class Task < ActiveRecord::Base
has_many :hunt_tasks
has_many :hunts, :through => :hunt_tasks#, :foreign_key => :hunt_id
attr_accessible :name
validates :name, :presence => true,
:length => { :maximum => 50 } ,
:uniqueness => { :case_sensitive => false }
end
class HuntTask < ActiveRecord::Base
belongs_to :hunts # the id for the association is in this table
belongs_to :tasks
end
When you create an association between 2 of your models, you add functionality to them, depending on how you define your relationship. Each type kinda adds different functions to your model.
I really recommend reading this guide -> http://guides.rubyonrails.org/association_basics.html
Here you can see which functions get added by each different type of association.
http://guides.rubyonrails.org/association_basics.html#detailed-association-reference
If I do a small sample program like...
class HuntsController < ApplicationController
# GET /hunts
# GET /hunts.json
def index
#hunts = Hunt.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #hunts }
end
end
# GET /hunts/1
# GET /hunts/1.json
def show
#hunt = Hunt.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #hunt }
end
end
# GET /hunts/new
# GET /hunts/new.json
def new
#hunt = Hunt.new
3.times do |i|
t = #hunt.hunt_tasks.build
t.name = "task-#{i}"
end
respond_to do |format|
format.html # new.html.erb
format.json { render json: #hunt }
end
end
# GET /hunts/1/edit
def edit
#hunt = Hunt.find(params[:id])
end
# POST /hunts
# POST /hunts.json
def create
#hunt = Hunt.new(params[:hunt])
respond_to do |format|
if #hunt.save
format.html { redirect_to #hunt, notice: 'Hunt was successfully created.' }
format.json { render json: #hunt, status: :created, location: #hunt }
else
format.html { render action: "new" }
format.json { render json: #hunt.errors, status: :unprocessable_entity }
end
end
end
# PUT /hunts/1
# PUT /hunts/1.json
def update
#hunt = Hunt.find(params[:id])
respond_to do |format|
if #hunt.update_attributes(params[:hunt])
format.html { redirect_to #hunt, notice: 'Hunt was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #hunt.errors, status: :unprocessable_entity }
end
end
end
# DELETE /hunts/1
# DELETE /hunts/1.json
def destroy
#hunt = Hunt.find(params[:id])
#hunt.destroy
respond_to do |format|
format.html { redirect_to hunts_url }
format.json { head :no_content }
end
end
end
and this model-relation
class Hunt < ActiveRecord::Base
has_many :hunt_tasks
end
class HuntTask < ActiveRecord::Base
belongs_to :hunt
end
and add this snippet somewhere in views/hunts/_form.html
<% #hunt.hunt_tasks.each do |t| %>
<li><%= t.name %></li>
<% end %>
I get regular output, seeing that the 3 tasks were created.
have you tried
hunttask = #hunt.build_hunt_task
in the HuntsController new action?
http://guides.rubyonrails.org/association_basics.html#detailed-association-reference
The immediate error you are seeing is in app/views/shared/_error_messages.html.erb. object is not defined, You probably need to find where that partial is called. Find:
render :partial=>"/shared/error"
replace it with
render :partial=>"/shared/error", :locals=>{:object=>#hunt}
If you find it in app/views/hunts somewhere, if you find in in app/views/tasks, replace #hunt with #task
That will at least show you what the real error is.

Resources