When testing my controller I have a spec in Rspec using factorygirl for the data;
describe 'POST #create' do
context 'with valid attributes' do
it 'saves the new child in the database' do
expect{
post '/children', :create, person: attributes_for(:child)
}.to change(Person, :count).by(1)
end
I get an error
1) PeopleController POST #create with valid attributes saves the new child in the database
Failure/Error: post '/children', :create, person: attributes_for(:child)
NoMethodError:
undefined method key?' for :create:Symbol
# ./spec/controllers/people_controller_spec.rb:46:inblock (5 levels) in '
# ./spec/controllers/people_controller_spec.rb:45:in `block (4 levels) in '
My controller(this is everything so sorry about the irrelevant stuff)
class PeopleController < ApplicationController
before_action :set_person, only: [:show, :edit, :update, :destroy]
before_action :set_type
def full_name
[Person.christian_name, Person.surname].join ' '
end
def index
#people = type_class.all
end
def new
#person = type_class.new
end
def update
end
def show
end
def edit
end
def create
if #person_type == 'Child'
#person = Child.new(person_params)
else
#person = CareGiver.new(person_params)
end
if #person.save
redirect_to #person
else
render 'new'
end
end
def destroy
end
private
def person_params
if #person_type == 'Child'
params.require(:child).permit(:type, :christian_name, :surname, :dob, :gender)
else
params.require(:care_giver).permit(:type, :christian_name, :surname, :email,
:password, :password_confirmation)
end
end
def set_type
#person_type = type
end
def type
Person.types.include?(params[:type]) ? params[:type] : "Person"
end
def people_types
%w[Person Child Provider CareGiver]
end
def type_class
type.constantize if type.in? people_types
end
def set_person
#person = type_class.find(params[:id])
end
end
This is saving different types of person to a single table (sti).
It seems as though no params are passed to the controller. When I use debugger at this point I can create a Child object, but post doesn't pass it on. I added '/children' to get the correct route (although I read that rspec controller tests bypass the routes stack)
I have stared at it for hours and have that feeling in my stomach you get from not knowing why!
Thanks for any help
def set_type
#person_type = type
end
def type
Person.types.include?(params[:type]) ? params[:type] : "Person"
end
Found this code in my controller. the type method is not setting a value that #person_type could use. I got rid of method type, and change set_type to
def set_type
#person_type = params[:person][:type]
end
Not sure if this is safe? but now the test passes.
p.s. All the other tests broke!!
Related
I have seen this error, I understand the problem or so I hope I do, the problem being my order_items are saving before an Order Id has been created. The problem of being a nube is having a clue about the problem but not idea about how to implement the solution, your patience is appreciated.
The error I am getting.
ActiveRecord::RecordNotSaved (You cannot call create unless the parent is saved):
app/models/shopping_bag.rb:22:in add_item'
app/controllers/order_items_controller.rb:10:increate'
My OrderItems controller
class OrderItemsController < ApplicationController
def index
#items = current_bag.order.items
end
def create
current_bag.add_item(
book_id: params[:book_id],
quantity: params[:quantity]
)
redirect_to bag_path
end
def destroy
current_bag.remove_item(id: params[:id])
redirect_to bag_path
end
end
My Orders controller
class OrdersController < ApplicationController
before_action :authenticate_user!, except:[:index, :show]
def index
#order = Order.all
end
def new
#order = current_bag.order
end
def create
#order = current_bag.order
if #order.update_attributes(order_params.merge(status: 'open'))
session[:bag_token] = nil
redirect_to root_path
else
render new
end
end
private
def order_params
params.require(:order).permit(:sub_total, :status, :user_id)
end
end
My shopping bag Model
class ShoppingBag
delegate :sub_total, to: :order
def initialize(token:)
#token = token
end
def order
#order ||= Order.find_or_create_by(token: #token, status: 'bag') do | order|
order.sub_total = 0
end
end
def items_count
order.items.sum(:quantity)
end
def add_item(book_id:, quantity: 1)
book = Book.find(book_id)
order_item = order.items.find_or_create_by(
book_id: book_id
)
order_item.price = book.price
order_item.quantity = quantity
ActiveRecord::Base.transaction do
order_item.save
update_sub_total!
end
end
def remove_item(id:)
ActiveRecord::Base.transaction do
order.items.destroy(id)
update_sub_total!
end
end
private
def update_sub_total!
order.sub_total = order.items.sum('quantity * price')
order.save
end
end
Thank you, your time is appreciated.
From docs about find_or_create_by:
This method always returns a record, but if creation was attempted and failed due to validation errors it won’t be persisted, you get what create returns in such situation.
Probably this is the situation - the record was not persisted in a database, but only created in memory. Looking at your code, I think you want to use a bang-version of the method (find_or_create_by!), which will raise an error in such situation.
To use parent attributes in child when using nested_attributes you can use inverse_of. Here is the documentation which may help you understand why parents need to be created first.
UPDATED with example: This will create forums first and then posts.
class Forum < ActiveRecord::Base
has_many :posts, :inverse_of => :forum
end
class Post < ActiveRecord::Base
belongs_to :forum, :inverse_of => :posts
end
I'm creating a controller in Rails, and I'm looking for ways to have different strong parameters for different controller methods
In update and new actions, I would want to require post
params.require(:post).permit(:body, :is_public, :title, :id)
But in post/index, i don't need to require these parameters.
How do you make different requirements strong parameters for different controller methods?
Your "strong parameters methods" are just Ruby methods. You can have however many you want.
class PostsController < ApplicationController
def create
#post = Post.new(create_params)
end
def update
#post = Post.find(params[:id])
if #post.update(update_params)
# ...
else
# ...
end
end
private
def base_params
params.require(:post)
end
# Don't take IDs from the user for assignment!
def update_params
base_params.permit(:body, :title)
end
def create_params
base_params.permit(:body, :title, :foo, :bar)
end
end
You can also name them whatever you want. Calling it [resource_name]_params is just a scaffolding convention.
Just do something like
class FooController < ApplicationController
def create
#post = Post.new(create_params)
if #post.save
blah
else
blah
end
end
def index
... something else
end
private
def create_params
params.require(:post).permit(:body, :is_public, :title, :id)
end
end
I'd like to ask why i'm always getting nil value when running rspec controller test ?
I already read in this site and most of answers because plurals word using inside assigns
but in my case thats not working and i still got the same value
This is my Controller
class ContactsController < ApplicationController
load_and_authorize_resource
before_action :find_contact, only: [:show,:edit,:update,:destroy]
def index
authorize! :index, Contact
#contact = Contact.accessible_by(current_ability)
# #contact = Contact.all
end
def show
end
def new
#contact = Contact.new
end
def edit
end
def create
#contact = current_user.contact.new(contact_params)
if #contact.save
redirect_to #contact
else
render 'new'
end
end
def update
# #contact = Contact.find(params[:id])
if #contact.update(contact_params)
redirect_to #contact
else
render 'edit'
end
end
def destroy
#contact.destroy
redirect_to contacts_path
end
private
def contact_params
params.require(:contact).permit(:firstname,
:lastname,
:alamat,
details_attributes: [:id, :number, :_destroy])
end
def find_contact
#contact = Contact.find(params[:id])
end
end
And this is my simple controller test
require 'rails_helper'
RSpec.describe ContactsController do
describe "Contact" do
it "succesfully create the contact" do
contact = FactoryGirl.create(:contact)
get :index
# byebug
expect(assigns(:contacts)).to eq([contact])
end
end
end
Even i change assigns(:contacts) to assigns(:contact) i still got the same value. So where is that i am do wrong ?
Please kindly answer this, big thanks
Even i change assigns(:contacts) to assigns(:contact) i still got the
same value. So where is that i am do wrong ?
assigns and assert_template have been remove and extracted to a gem in Rails 5.
Source
You have an authorization check
authorize! :index, Contact
before the assignment to #contact.
But your test has no setup in order to grant permissions to the requesting user in any way.
It probably makes sense to have an additional test alongside the one you show in order to spot errors like this. E.g:
it "returns 200 (OK)" do
get :index
expect(response.response_code).to eq(200)
end
I am making a basic account setup and to try to learn how the database stuff works. I have been running into this error constantly, and I have no idea how to make it disappear. I have my stuff named as U, so the URL will be easier to type a username like Reddit has it example.com/u/username
The Error is uninitialized constant UController::User_param
It highlights this code: #user = U.new(User_param)
Controller:
class UController < ApplicationController
def index
#users = U.all
end
def show
end
def create
#user = U.new(User_param)
if #user.save
redirect_to :action => 'list'
else
#user = U.all
render :action => 'new'
end
end
def User_param
params.require(:Us).permit(:id, :email, :password, :created_at, :updated_at)
end
def new
#user = U.new
end
def edit
end
end
Routes:
resources :u
U Model:
class U < ActiveRecord::Base
end
In Rails you don't capitalize methods, only constants and classes. change User_param to user_params along with the method and that should work. I made params plural since it is clearer and easier to understand
Also, change the user_param method to this:
def user_params
params.require(:u).permit(:id, :email, :password, :created_at, :updated_at)
end
The .require(:u) doesn't need to be plural as you had it.
My controller test isn't passing. I have a before_acton in my ReviewsController that tests for a current user. If there is none, it shouldn't complete the create action and redirect. But it does create it, despite the session[:user_id] being nil. Here is my code:
it "doesnt create review if there is valid input, but not authenticated" do
video = Video.create(title: "new title", description: "new description")
review = Review.create(content: "content1", rating: 1)
expect(Review.count).to eq(0)
end
In my ReviewsController:
def create
#video = Video.find(params[:video_id])
#review = Review.new(parameters)
rating = params[:review][:rating]
content = params[:review][:content]
#review.content = content
#review.rating = rating
#review.user_id = current_user.id
#review.video = #video
#review.save
redirect_to #video
end
i have a before_action in the Reviews controller that test if a user is authenticated. If the session[:user_id] is nil, then it redirects to the sign_in_path. Here are the methods:
def current_user
#current_user = User.find(session[:user_id]) if session[:user_id]
end
def require_user
redirect_to sign_in_path unless current_user
end
in my reviews_controller_spec.rb file, it shouldn't create the review, because it shouldn't get past the before_action :require_user
Why is the review object still being created? Why doesn't the before_action filter stop it? The test fails and just says it expected Review.count to be 0, but got 1.
# config/routes.rb
resources :videos, shallow: true do
resources :reviews
end
There is no need to bind params to attributes 1-1. In fact doing so will make you controllers excessively verbose plus its boring as hell to type it out.
class ReviewsController < ApplicationController
before_filter :authenticate_user!
def create
#video = Video.find(params[:video_id])
#review = current_user.reviews.new(review_params) do |r|
r.video = #video
end
end
private
# this is mass assignment protection
def review_params
params.require(:review).permit(:rating, :content)
end
end
If you decide to roll you own authentication (which is a bad idea) don't repeat it across your controllers and views:
module AuthenticationHelper
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def sign_in(user)
#current_user = user
session[:user_id] = user.id
end
def sign_out
#current_user = nil
reset_session
end
def authenticate_user!
raise User::NotAuthorized unless current_user
end
end
A key point is that a opt out security model is far better since it eliminates the risk of leaving a route open by omission:
class ApplicationController
include AuthenticationHelper
rescue_from User::NotAuthorized, with: :deny_access!
# use skip_before_action :authenticate_user! if
# you don't want a route / controller to require auth.
before_action :authenticate_user!
private
def deny_access!
redirect_to root_path
end
end
So to test ReviewsController we would do:
describe ReviewsController do
let(:user) { User.create(name: 'joe') }
let(:video) { Video.create(name: 'Cats Gone Wild') }
describe "#create" do
let(:valid_attributes) { { video_id: video, title: "new title", description: "new description" } }
context "when unauthorized" do
it "does not create a review" do
expect { post :create, valid_attributes }.to_not change(Review, :count)
end
end
context "when authorized" do
before { subject.stub(:current_user) { user } }
# ...
end
end
end
Key points here:
use let to setup test dependencies
use expect { action }.to change to test if an action alters the DB. Testing for .count == 0 can lead to false positives.