I'm currently working with a small team on an open source Rails 4 chess application, and I'm trying to test out various possible piece moves in RSpec (including special cases such as en passant and castling). A senior web developer suggested that I use a separate table to keep track of the moves taken in each game of chess. After taking him up on his suggestion, I'm having trouble testing out valid moves, as shown in the error message below. I suspect that it might be a problem with my associations, but the teammates that I was able to talk to about this were unsure about the cause.
Any help would be greatly appreciated.
Error message:
Failures:
1) PiecesController Action: pieces#update should create a move when a move is valid
Failure/Error: #current_game ||= current_piece.game
NoMethodError:
undefined method `game' for nil:NilClass
# ./app/controllers/pieces_controller.rb:36:in `current_game'
# ./app/controllers/pieces_controller.rb:40:in `require_authorized_for_current_game'
# ./spec/controllers/pieces_controller_spec.rb:12:in `block (3 levels) in <top (required)>'
The test:
RSpec.describe PiecesController, type: :controller do
describe "Action: pieces#update" do
it "should create a move when a move is valid" do
user_sign_in
game = FactoryGirl.create(:game)
# Test a white pawn's movement on its first turn:
piece = FactoryGirl.create(:piece)
move = FactoryGirl.create(:move)
# Why can't I call game.id below?
patch :update, :id => game.id, :pieces => { }
piece_params = { :x_position => piece.x_position, :y_position => piece.y_position, :type => "Pawn" }
if piece.valid_move?(piece_params)
...
end
end
end
private
def user_sign_in
user = FactoryGirl.create(:user)
sign_in user
end
end
Associations:
class Game < ActiveRecord::Base
has_many :pieces
has_many :moves, through: :pieces
belongs_to :white_player, class_name: 'User', foreign_key: :white_player_id
belongs_to :black_player, class_name: 'User', foreign_key: :black_player_id
...
end
class Piece < ActiveRecord::Base
belongs_to :game
has_many :moves
def valid_move?(params)
...
end
...
end
class Move < ActiveRecord::Base
belongs_to :piece
end
Factories:
FactoryGirl.define do
factory :user do
...
end
factory :game do
association :white_player, factory: :user
association :black_player, factory: :user
turn 1
end
factory :piece do
association :game
...
end
# Set up an initially empty move, then adjust the values after checking that a piece can be moved:
factory :move do
association :piece
...
end
end
The controller:
class PiecesController < ApplicationController
before_action :authenticate_user!
before_action :require_authorized_for_current_game, only: [:update]
before_action :require_authorized_for_current_piece, only: [:update]
def update
...
end
...
private
def current_piece
#current_piece ||= Piece.find_by_id(params[:id])
end
...
def piece_params
params.require(:piece).permit(:x_position, :y_position, :type, :captured)
end
def current_game
#current_game ||= current_piece.game
end
def require_authorized_for_current_game
if current_game.white_player != current_user && current_game.black_player != current_user
render text: 'Unauthorized', status: :unauthorized
end
end
end
Related
Just trying to create a simple workout log here I'm getting this error when I try and create weights
"Workout ID must exist"
Models
class Workout < ApplicationRecord
belongs_to :user
has_many :exercises
has_many :weights, through: :exercises
accepts_nested_attributes_for :exercises, :allow_destroy => true
accepts_nested_attributes_for :weights, :allow_destroy => true
validates :bodypart, presence: true, length: { maximum: 255 }
end
class Weight < ApplicationRecord
belongs_to :exercise
belongs_to :workout
validates :amount, presence: true, length: { maximum: 255 }
validates :reps, presence: true, length: { maximum: 255 }
end
class Exercise < ApplicationRecord
belongs_to :workout
has_many :weights
accepts_nested_attributes_for :weights
validates :name, presence: true
validates_associated :weights
end
After reading a few things I thought that a has many through would be the option for these associations but now I'm not so sure. When I try and create a weight for exercise I get the exercise ID but can't seem to get the workout ID despite a number of attempts. I'm not sure if its simply a controller issue or if Im missing something bigger here.
My Current Weights Controller
class WeightsController < ApplicationController
before_action :authenticate_user!
def new
#weight = Weight.new
end
def create
#exercise = Exercise.find(params[:exercise_id])
#weight = #exercise.weights.build(weight_params)
if #weight.save
flash[:notice] = "Set saved"
redirect_to exercise_path(#exercise)
else
redirect_to exercise_path(#exercise)
flash[:notice] = "#{#weight.errors.full_messages.to_sentence}"
end
end
def edit
end
def update
end
def destroy
weight = Weight.find(params[:id])
#weight.destroy
redirect_to root_path
end
private
def weight_params
params.require(:weight).permit(:amount, :reps)
end
end
Routes.rb
Rails.application.routes.draw do
devise_for :users, controllers: { omniauth_callbacks: "callbacks" }
root 'articles#index'
get '/demo', to: 'static_pages#demo'
get '/about', to: 'static_pages#about'
get '/test', to: 'static_pages#index'
resources :articles
resources :workouts do
resources :exercises
end
resources :exercises do
resources :weights
end
resources :workouts do
member do
put :is_finished
put :unfinish
end
end
resources :exercises do
member do
put :is_finished
put :unfinish
end
end
resources :books, :only => :index
end
Exercises controller
class ExercisesController < ApplicationController
before_action :authenticate_user!
# before_action :set_exercise, except: [:index, :new, :create]
def new
#exercise = Exercise.new
#exercise.weights.new
end
def index
#workout = Workout.find(params[:workout_id])
#exercise = #workout.exercises
end
def create
#workout = Workout.find(params[:workout_id])
#exercise = #workout.exercises.build(exercise_params)
if #exercise.save
redirect_to workout_exercise_path(#workout, #exercise)
flash[:notice] = "Exercise created, enter weight and reps for your first set"
else
redirect_to #workout
flash[:notice] = "#{#exercise.errors.full_messages.to_sentence}"
end
end
def edit
end
def update
#exercise = Exercise.find(params[:id])
if #exercise.update_attributes(exercise_params)
redirect_to exercise_path(#exercise)
else
flash[:notice] = "Something went wrong"
end
end
def show
#exercise = Exercise.find(params[:id])
#weights = #exercise.weights.order('created_at DESC').paginate(page: params[:page], per_page: 5)
end
I do have an ID column for workout_id set on my Weights model but it always populates NULL whenever I can manage to create an exercise. Also, in the rails console I can find a workout and call #workout.weights and it returns the weights associated with the workout just fine. I just can't get the workout ID to populate in Weight model. Wondering if has and belongs to many would be better OR if my has many through is just set up wrong. I did try "inverse_of" without any luck. Any help would be appreciated.
The answer is quite simple. To add reverse functionality, you have to explicitly state it, through the exercise.
in weight.rb remove belongs_to :workout and add the following
def workout
self.exercise.workout
end
Perhaps you will need to add logic to avoid nilClass errors.
Now you do have #weight.workout through #weight.exercise
I am getting this error message from Rspec
Failure/Error: #address = #owner.addresses.build
NoMethodError:
undefined method `build' for #<Array:0x007f9faba657f0>
Below is snippet from my controller
class AddressesController < ApplicationController
def new
#owner = Owner.find(params[:owner_id])
#address = #owner.addresses.build
end
end
Below is snippet from my spec file:
describe "GET #new" do
let(:owner) { create(:owner) }
before { xhr :get, :new, owner_id: owner.id }
it 'response will be success' do
expect(response).to be_success
end
end
Below is my route for addresses#new
new_owner_address GET /owners/:owner_id/addresses/new(.:format) addresses#new
addresses.build method is working fine in console and working fine in my application but it is failing on spec. Any suggestion will be highly appreciated.
Update:
factory:
FactoryGirl.define do
factory :owner do
name 'foo'
address 'bar'
amount 200.00
country 'foobar'
state 'qax'
end
end
Model and Associations:
class Owner < ActiveRecord::Base
has_many :addresses, dependent: :destroy
end
class Address < ActiveRecord::Base
belongs_to :owner
end
#owner.addresses has to be an ActiveRecord::Relation in order to respond to #build. However, it seems to return an Array in your spec. Does your factory override the default association somehow?
I'm getting an error when trying to test a Rails Controller with RSpec. It's a double nested route and I'm trying to figure out the right syntax but hadn't had many luck yet.
The error that I'm getting is
Failure/Error: get :index, {category_id: category.to_param, id: system.to_param}
ActionController::UrlGenerationError:
No route matches {:action=>"index", :category_id=>"220", :controller=>"reviews", :id=>"166"}
# ./spec/controllers/reviews_controller_spec.rb:11:in `block (3 levels) in <top (required)>'
I've made the same test for the system controller which works fine. The webpage works fine as well. No errors with that (just this error with testing).
Here is what the RSpec test look like:
require 'rails_helper'
RSpec.describe ReviewsController, type: :controller do
let (:category) { create(:category) }
let (:system) { create(:system) }
let (:reviews) { create_list(:review, 3, category: category, system: system) }
describe "GET index" do
it "assigs all reviews to an instance var called #reviews" do
get :index, {category_id: category.to_param, id: system.to_param}
expect(assigns(:reviews)).to eq reviews
end
it "assigns all the reviews to an var called #system" do
get :index, system_id: system.to_param
expect(assigns(:system)).to eq system
end
end
describe "system scope" do
before { create(:review) }
it "only assigns reviews index in the current system" do
get :index, {category_id: category.to_param, id: system.to_param}
expect(assigns(:reviews)).to eq reviews
end
end
end
This is the Controller that it's testing:
class ReviewsController < ApplicationController
def index
#system = System.find(params[:system_id])
#reviews = #system.reviews
respond_to do |format|
format.html
format.json { render json: { system: #system, reviews: #reviews } }
end
end
def show
#system = System.find(params[:system_id])
#review = #system.reviews
end
end
And these are the routes:
Rails.application.routes.draw do
root "categories#index"
resources :categories do
resources :systems do
resources :reviews
end
end
end
Here are the models:
Category Model
class Category < ActiveRecord::Base
validates_presence_of :name
has_many :systems
end
System Model
class System < ActiveRecord::Base
belongs_to :category
has_many :reviews
validates_presence_of :name, :category
end
Review Model
class Review < ActiveRecord::Base
belongs_to :system
validates_presence_of :content, :system
end
ActionController::UrlGenerationError: No route matches
{:action=>"index", :category_id=>"220", :controller=>"reviews",
:id=>"166"}
According to your routes, that particular index route expects category_id and system_id as keys. You need to change :id to :system_id. The below should work.
it "only assigns reviews index in the current system" do
get :index, {category_id: category.to_param, system_id: system.to_param}
expect(assigns(:reviews)).to eq reviews
end
Update
NoMethodError: undefined method `category=' for
Review:0x005578a9e87188
There is no association between a review and a category. Edit your models to set the association accordingly.
#category.rb
class Category < ActiveRecord::Base
validates_presence_of :name
has_many :systems
has_many :reviews
end
#review.rb
class Review < ActiveRecord::Base
belongs_to :system
belongs_to :category
validates_presence_of :content, :system
end
I have a comments_controller that uses inherited_resources and deals with this models:Comment (belongs_to Shop and belongs_to User), and Shop (belongs_to User).
Rails 4.1.1 and Inherited_resources v is 1.5.0.
Routes are:
resources :shop do
resources :comments, only: [:create, :destroy]
end
However, the below code doesn't work:
class CommentsController < InheritedResources::Base
before_filter :authenticate_user!
nested_belongs_to :user, :shop
actions :create, :destroy
def create
#comment = build_resource
#comment.shop = Shop.find(params[:hotel_id])
#comment.user = current_user
create!
end
def destroy
#hotel = Shop.find(params[:hotel_id])
#comment = Comment.find(params[:id])
#comment.user = current_user
destroy!
end
private
def permitted_params
params.permit(:comment => [:content])
end
Rspec that test creation/deletion of comments tell me Couldn't find User without an ID.
Thanks for any help.
UPD One of the failing tests:
let(:user) { FactoryGirl.create(:user) }
let(:shop) { FactoryGirl.create(:shop, user: user) }
describe "comment creation" do
before { visit shop_path(shop) }
describe "with invalid information" do
it "should not create a comment" do
expect { click_button "Post a comment" }.not_to change(Comment, :count)
end
end
From your routes, it looks like you want to deal with Comments belonging to a Shop. In this case, you don't need nested_belongs_to, instead change it to belongs_to :shop in your controller and that will take care of it. And add another line belongs_to :user separately.
So, your controller will look like this:
class CommentsController < InheritedResources::Base
before_filter :authenticate_user!
belongs_to :shop
belongs_to :user
actions :create, :destroy
.
.
.
end
I spent most of the day trying to root out a problem with a controller spec, and the current workaround seems unacceptable to me. Any take on why this works? ... and what I should do instead.
Given a simple hierarchy as follows, and the following ability.rb, the properties_controller_spec.rb does not allow the spec below to pass without the line saying:
ability = Ability.new(subject.current_user)
Can you tell me why this would be?
Thanks!
Models:
class Account < ActiveRecord::Base
has_many :properties, :dependent => :nullify
end
class Property < ActiveRecord::Base
belongs_to :account
end
class User < Refinery::Core::BaseModel #for RefineryCMS integration
belongs_to :account
end
Ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.has_role? :user
can [:read, :create, :update, :destroy], Property, account_id: user.account_id
else
can [:show], Property
end
end
end
properties_contoller_spec.rb:
require 'spec_helper'
describe PropertiesController do
def valid_attributes
describe "Authenticated as Property user" do
describe "PUT update" do
describe "with invalid params" do
it "re-renders the 'edit' template" do
property = FactoryGirl.create(:property, account: property_user.account)
# Trigger the behavior that occurs when invalid params are submitted
Property.any_instance.stub(:save).and_return(false)
ability = Ability.new(subject.current_user) # seriously?
put :update, {:id => property.to_param, :property => { }}, {}
response.should render_template("edit")
end
end
end
end
end
Arg! Found it myself.
Here it is:
config.include Devise::TestHelpers, :type => :controller
Following is the code to sign in the property_user, as directed by the Devise docs. (The locals in question are created in a global_variables.rb that is included. These are used all over the place.)
def signed_in_as_a_property_user
property_user.add_role "User"
sign_in property_user
end
def sign_in_as_a_property_user
property_user.add_role 'User'
post_via_redirect user_session_path,
'user[email]' => property_user.email,
'user[password]' => property_user.password
end