I am having trouble making a test pass for my posts controller.
What is the proper way to test my create action inside the Posts controller with a polymorphic association?
I'm using Rails 4
Here's my code
Models:
class Topic < ActiveRecord::Base
belongs_to :user
belongs_to :category
has_many :posts, as: :postable
has_many :posts, dependent: :destroy
validates :name, presence: true, length: {maximum: 50}
validates :description, presence: true, length: {maximum: 80}
validates :user_id, presence: true
is_impressionable
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :topic
belongs_to :category
belongs_to :postable, polymorphic: true
has_many :posts, as: :postable
validates :comment, presence: true
validates :user_id, presence: true
validates :topic_id, presence: true
validates :category_id, presence: true
default_scope { order(created_at: :asc) }
end
Post controller
class PostsController < ApplicationController
before_action :auth_user
before_action :set_post, only: [:destroy]
before_action :correct_user, only: [:destroy]
before_action :find_postable, only: [:create, :new]
def new
#post = #postable.posts.build
end
def create
#post = #postable.posts.build(post_params)
set_topic_id
set_category_id
#post.user_id = current_user.id
if #post.save
redirect_to topic_path(#post.topic.id)
else
redirect_to request.referer, notice: "Post unsuccessful!"
end
end
def destroy
#post.destroy
flash[:success] = 'Post deleted'
redirect_to request.referer || root_url
end
private
def set_post
#post = Post.find(params[:id])
end
def correct_user
#post = current_user.posts.find_by(id: params[:id])
redirect_to root_url if #post.nil?
end
def find_topic
#topic = Topic.find(params[:topic_id])
end
def find_postable
#postable = Post.find_by_id(params[:post_id]) if params[:post_id]
#postable = Topic.find_by_id(params[:topic_id]) if
params[:topic_id]
end
def post_params
params.require(:post).permit(:comment)
end
end
Post controller test:
require 'test_helper'
class PostsControllerTest < ActionController::TestCase
def setup
#topic = topics(:topicone)
#post = posts(:postone)
#posttwo = posts(:posttwo)
#category = categories(:categoryone)
#user = users(:user1)
end
test 'should create post when logged in' do
sign_in #user
assert_difference 'Post.count', 1 do
post :create, post: { user_id: #user.id, category_id:
#category.id, topic_id: #topic.id, comment: "First reply!",
postable_id: #post.id, postable_type: "Post" }
end
end
end
When I run the test above I get this error:
ERROR["test_should_create_post_when_logged_in", PostsControllerTest,
2016-12-04 14:23:25 -0500]
test_should_create_post_when_logged_in#PostsControllerTest
(1480879405.93s)
NoMethodError: NoMethodError: undefined method `posts' for nil:NilClass
app/controllers/posts_controller.rb:13:in `create'
test/controllers/posts_controller_test.rb:28:in `block (2
levels) in <class:PostsControllerTest>'
test/controllers/posts_controller_test.rb:25:in `block in
<class:PostsControllerTest>'
app/controllers/posts_controller.rb:13:in `create'
test/controllers/posts_controller_test.rb:28:in `block (2 levels)
in <class:PostsControllerTest>'
test/controllers/posts_controller_test.rb:25:in `block in
<class:PostsControllerTest>'
From my understanding, I believe it's telling me that it can't find whether the create action is posting to a post or to a topic.
The site works great in development and production. The problem is in this test.
How can I rewrite this test and make it so it recognizes to whom it's posting to?
I found a solution for this shortly after.
In this case, to successfully test if a post is created on a topic, make sure to provide the topic_id.
test 'should create post on a topic when logged in' do
sign_in #user
assert_difference 'Post.count', 1 do
post :create, topic_id: #topic, post: {
user_id: #user.id,
category_id: #category.id,
comment: 'First post on a topic!' }
end
end
Now to test if a post is created on another post, make sure to provide the post_id.
test 'should create post on another post when logged in' do
sign_in #user
assert_difference 'Post.count', 1 do
post :create, post_id: #post, post: {
user_id: #user.id,
category_id: #category.id,
comment: 'First post on a topic!' }
end
end
Related
New to RoR, here couldn't find the solution I was looking for so I'm typing my problem here:
I have a web app where after the user is logged in they will be redirected to a dashboard that shows them their various information.
For now - as I'm getting the feel of RoR - the view of the dashboard was suppose to get a lot of information:
def index
#logged_user = Person.includes(:user, :addresses).find(session[:user_id])
end
but when I checked using #logged_user.inspect it only gave me this
#<Person (person model attributes here) >
I checked the log and it seems the User and Addresses were loaded
printscreen of my log http://prntscr.com/c40fee
but they aren't in the #logged_user variable... did I miss a step here? where did I go wrong?
EDIT:
here's the model of Person
class Person < ActiveRecord::Base
validates :first_name, :last_name, presence: true
has_one :user, :dependent => :destroy
has_one :medical_record
has_many :addresses, :dependent => :destroy
has_many :contacts,:dependent => :destroy
has_many :enrollments_as_student, :class_name => 'Enrollment', :foreign_key => :student_id, :dependent => :destroy
accepts_nested_attributes_for :addresses, allow_destroy: true
accepts_nested_attributes_for :contacts, allow_destroy: true
accepts_nested_attributes_for :medical_record, allow_destroy: true
end
model for User
class User < ActiveRecord::Base
validates :username, presence: true
validates :username, :password, format: {with: /\A[a-zA-Z0-9 ñÑ]+\Z/, message: "Special characters aren't allowed"}
validates :username, uniqueness: {message: "username is already taken"}
has_secure_password
belongs_to :person
end
model for Addresses
class Address < ActiveRecord::Base
validates :street, :barangay, :city, :province,
presence: {message: "Fill in all necessary fields"}
validates :zipcode,
numericality: {message: "Zipcodes should only contain numbers"},
length: {minimum: 4, maximum: 4, message: "Invalid zipcode length"}
belongs_to :person
end
also snippet for the login
def login
#user = User.find_by_username(params[:receptions][:username])
if #user && #user.authenticate(params[:receptions][:password])
session[:user_id] = #user.person_id
flash[:notice] = "You have succfully logged in"
redirect_to '/dashboards'
else
flash[:notice] = "Invalid username or password"
redirect_to '/login'
end
Following a tutorial for adding favorites to my existing project, whereby users can favorite property listings but can't get past the error below:
Routing error,uninitialized constant FavoriteRoomsController
favoriterooms_controller.rb
class FavoriteRoomsController < ApplicationController
before_action :set_room
def create
if Favorite.create(favorited: #room, user: current_user)
redirect_to #room, notice: 'Room has been favorited'
else
redirect_to #room, alert: 'Something went wrong...*sad panda*'
end
end
def destroy
Favorite.where(favorited_id: #room.id, user_id: current_user.id).first.destroy
redirect_to #room, notice: 'Room is no longer in favorites'
end
private
def set_room
#room = Room.find(params[:room_id] || params[:id])
end
end
room.rb
class Room < ActiveRecord::Base
belongs_to :user
has_many :photos
has_many :favorites
geocoded_by :address
after_validation :geocode, if: :address_changed?
validates :home_type, presence: true
validates :room_type, presence: true
validates :accommodate, presence: true
validates :bed_room, presence: true
validates :bath_room, presence: true
validates :listing_name, presence: true, length: {maximum: 50}
validates :summary, presence: true, length: {maximum: 500}
validates :address, presence: true
validates :lister_type, presence: true
validates :gender_type, presence: true
validates :occupation_type, presence: true
validates :move_in, presence: true
validates :term, presence: true
validates :term, presence: true
end
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :omniauthable
validates :fullname, presence: true, length: {maximum: 50}
has_many :rooms
has_many :favorites
has_many :favorite_rooms, through: :favorites, source: :favorited, source_type: 'Room'
def self.from_omniauth(auth)
user = User.where(email: auth.info.email).first
if user
return user
else
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.fullname = auth.info.name
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.image = auth.info.image
user.password = Devise.friendly_token[0,20]
end
end
end
end
favorite.rb
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :favorited, polymorphic: true
end
routes.rb
Rails.application.routes.draw do
root 'pages#home'
devise_for :users,
:path => '',
:path_names => {:sign_in => 'login', :sign_out => 'logout', :edit => 'profile'},
:controllers => {:omniauth_callbacks => 'omniauth_callbacks',
:registrations => 'registrations'
}
resources :users, only: [:show]
resources :rooms
resources :photos
resources :conversations, only: [:index, :create] do
resources :messages, only: [:index, :create]
end
resources :favorite_rooms, only: [:create, :destroy]
end
roomshow.html.erb
favorites button link code added in my roomsshow.html.erb
<%- unless current_user.favorite_rooms.exists?(id: #room.id) -%>
<%= link_to 'Add to favorites', favorite_rooms_path(room_id: #room), method: :post %>
<%- else -%>
<%= link_to 'Remove from favorites', favorite_room_path(#room), method: :delete %>
<%- end -%>
favorite_rooms_path POST /favorite_rooms(.:format) favorite_rooms#create
favorite_room_path DELETE /favorite_rooms/:id(.:format) favorite_rooms
Screenshot of error message with full trace
I've watched various tututorials and followed numerous suggestions but dont seem to be able to solve the issue by myself.
Routing error,uninitialized constant FavoriteRoomsController
Rails follow naming conventions very strictly and for a good reason. It expects the file names to be in a snake case. So you should change the file name favoriterooms_controller.rb to favorite_rooms_controller.rb
In our Rails app, there are 3 models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
And here are the corresponding migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
EDIT: here is also our UsersController:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: [:index, :destroy]
def index
#users = User.paginate(page: params[:page], :per_page => 10)
end
def show
#user = User.find(params[:id])
#administrations = #user.administrations
#calendar = current_user.calendars.build if logged_in?
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email,
:password, :password_confirmation)
end
# Before filters
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.try(:admin?)
end
end
Once a user is logged in (authentication system already up and running), we want to display on his profile (users#show), all the calendars he has created.
We have seeded the database with the following instances:
User.create!(first_name: "Andy") # This user's id is 1.
Calendar.create!(name: "CalendarA")
Calendar.create!(name: "CalendarB")
Calendar.create!(name: "CalendarC")
Administration.create!(user_id: 1, calendar_id: 1, role: "Creator")
Administration.create!(user_id: 1, calendar_id: 2, role: "Editor")
Administration.create!(user_id: 1, calendar_id: 3, role: "Viewer")
Then, we have created a _administration.html.erb partial:
<li id="administration-<%= administration.id %>">
<span class="name"><%= administration.calendar_id %></span>
</li>
And included it in our user show.html.erb file:
<p><%= #user.first_name %>'s calendars</p>
<% if #user.administrations.any? %>
<%= render #administrations %>
<% end %>
And this is working, we get:
Andy's calendars:
1
2
3
However, what we would like to get for each user, is not only the ids of his calendars, but their names too, like this:
Andy's calendars:
1 CalendarA
2 CalendarB
3 CalendarC
So we tried to update the _administration.html.erb partial as follows:
<li id="administration-<%= administration.id %>">
<span class="name"><%= administration.calendar_id.name %></span>
</li>
Which results in the following error:
NoMethodError in UsersController#show
undefined method `name' for 1:Fixnum
Application Trace | Framework Trace | Full Trace
app/views/administrations/_administration.html.erb:2:in `_app_views_administrations__administration_html_erb__2225316747000531998_70329866860100'
app/views/users/show.html.erb:32:in `_app_views_users_show_html_erb___891585127045041471_70329832995240'
How can we access the "foreign" attribute name from the calendar model through the foreign key calendar_id in the join administration model?
administration.calendar.name should work if your associations are set up correctly.
Alternatively, you could add this method to Administration:
def calendar_name
calendar.name
end
and then just call administration.calendar_name
models/user.rb
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :remember_me, :username, :login
end
models/post.rb
class Post < ActiveRecord::Base
has_many :comments, dependent: :destroy
belongs_to :user
attr_accessible :user_id, :description, :title
end
models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
attr_accessible :body, :user_id
end
I have installed 'strong_parameters'. And trying to make out with it. Can any one please guide me for model and controller code for the same.
Remove attr_accessible from models.
And in the controller, lets take the example of PostsController, create a private method post_params where you will define accessible attributes of Post model
class PostsController < ApplicationController
def update
#post = Post.find(params[:id])
if #post.update(post_params)
redirect_to #post, notice: 'Post was successfully updated.'
else
render "edit"
end
end
private
def post_params
params.require(:post).permit(:user_id, :description, :title)
end
end
My model:
class Product < ActiveRecord::Base
has_many :product_images, dependent: :destroy
accepts_nested_attributes_for :product_images, :reject_if => lambda { |p| p['image'].blank? }, :allow_destroy => true
end
class ProductImage < ActiveRecord::Base
belongs_to :product
mount_uploader :image, ProductImageUploader
validates_presence_of :image
end
My controller:
def create
#product = Product.new(permitted_params.product)
if #product.save
redirect_to edit_admin_product_path(#product), notice: "success"
else
render :new
end
end
def update
#product = Product.find(params[:id])
if #product.update_attributes(permitted_params.product)
redirect_to edit_admin_product_path(#product), notice: "success"
else
render :edit
end
end
permitted_params:
class PermittedParams < Struct.new(:params)
def product
params.require(:product).permit(*product_attrs)
end
def product_attrs
[:name, :content, :stock, :list_price, :selling_price, :bonus, :is_added,
product_images_attributes: [:id, :image, :_destroy] ]
end
end
And parameters passed:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"eqweq1231sda+3T0131643guXybT75X6NqN1ng=", "product"=>{"name"=>"weqwe", "content"=>"qweq", "product_images_attributes"=>{"0"=>{"image"=>"1069218_513152615405563_1187314087_n.jpg", "_destroy"=>""}}, "stock"=>"", "list_price"=>"", "selling_price"=>"123", "bonus"=>""}, "commit"=>"submit"}
Obviously the image is pass to the params. but when create product it will rollback to alarm the image is empty(validate presence image in my ProductImage model).
If I delete the validation then create the product. I can successfully upload the images in update action. I have totally no idea what's the problem! Please help. Q_Q