Not being able to order by includes on Rails3 - ruby-on-rails

I have the following expression:
user.clocks.includes(:users, :runs => :user_runs).find_by_id(params[:id])
which seems to work fine. But when I add an orders, like this:
user.clocks.includes(:users, :runs => :user_runs).orders("users.names").find_by_id(params[:id])
it breaks with the following error:
ActiveRecord::ConfigurationError: Association named 'user_runs' was not found; perhaps you misspelled it?
app/controllers/clocks_controller.rb:19:in `show'
test/functional/clocks_controller_test.rb:21:in `__bind_1286475263_942556'
Any ideas why?
The model looks like this:
class Clock < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :runs
end
class Run < ActiveRecord::Base
belongs_to :clock
has_many :user_runs
has_many :users, :through => :user_runs
end
class UserRun < ActiveRecord::Base
belongs_to :run
belongs_to :user
end
Continuing with my investigation I've tried this:
ubiquitous_user.clocks.includes(:runs => :user_runs).find_by_id(params[:id])
and I've noticed the queries it's generating doesn't get user_runs at all. Something is odd.
I've created a set of tests to try to figure what was going on:
context "A graph of users, clocks, runs, etc" do
setup do
#users = []
10.times do
#users << Factory.create(:user)
end
#clocks = []
10.times do
#clocks << Factory.create(:clock, :users => #users)
end
#clocks.each do |clock|
10.times do
run = Factory.create :run, :clock => clock
#users.each do |user|
Factory.create :user_run, :run => run, :user => user
end
end
end
#user = #users.first
#clock = #clocks.first
end
should "find a clock" do
assert_not_nil #user.clocks.find(#clock.id)
end
should "find a clock with users" do
assert_not_nil #user.clocks.includes(:users).find(#clock.id)
end
should "find a clock with users and runs" do
assert_not_nil #user.clocks.includes(:users, :runs).find(#clock.id)
end
should "find a clock with users, runs and user_runs" do
assert_not_nil #user.clocks.includes(:users, :runs => :user_runs).find(#clock.id)
end
should "find a clock with users order by users.name" do
assert_not_nil #user.clocks.includes(:users).order("users.name").find(#clock.id)
end
should "find a clock with users and runs order by users.name" do
assert_not_nil #user.clocks.includes(:users, :runs).order("users.name").find(#clock.id)
end
should "find a clock with users, runs and user_runs order by users.name" do
assert_not_nil #user.clocks.includes(:users, :runs => :user_runs).order("users.name").find(#clock.id)
end
end
Every test but the last one pass. Is this not a bug?

Shouldn't it be
user.clocks.find_by_id(params[:id], :include => [:users, {:runs => :user_runs}])
?

I believe this is a bug, so I reported it here: https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/5768

Related

Rails 4/ Factory Girl - has_many/has_many factory and defining different objects

I'd like to define a factory for a has_many/has_many relationship (which I think I got it right) but I don't know how then to define attributes for each objects of these created factory.
Here is the homepage, it's a list of cards of deals
We assume the homepage view below is for a SIGNED-IN user (note current_user comes from Devise).
<% #deals.each do |deal| %>
#userdeal = UserDeal.where('user_id = ? AND deal_id = ?', current_user.id, deal.id).take
<div>
<div class="button">
<% if #userdeal.number_of_clicks = 4 %>you reached the maximum clicks
<% end %>
</div>
</div>
<% end %>
Basically if a user has for a specific deal (on the table user_deals, see below the models) a user_deal.number_of_clicks = 4 then, on the card a button will appear with a message like "you reached the maximum clicks". if nb of clicks <4, no button appears.
So I want to use the factory on a Feature test where I'll check that if I create with fatcory girl one object #userdeal1 where the user reached 4 clicks, on this card he sees the button and its text, but for other deals he sees nothing.
Here's what I have so far
models
class Deal < ActiveRecord::Base
has_many :user_deals, dependent: :destroy
has_many :users, through: :user_deals
end
class User < ActiveRecord::Base
has_many :user_deals
has_many :deals, through: :user_deals
end
class UserDeal < ActiveRecord::Base
belongs_to :user, :foreign_key => 'user_id'
belongs_to :deal, :foreign_key => 'deal_id'
end
And the structure of the table user_deal
# Table name: user_deals
#
# id :integer not null, primary key
# user_id :integer
# deal_id :integer
# number_of_clicks :integer default(0)
# created_at :datetime
# updated_at :datetime
So far I found how to create
FactoryGirl.define do
factory :user_deal do
association :user
association :deal
end
Following factorygirl ReadMe I created this:
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "person#{n}#example.com"}
password "vddfdf"
password_confirmation "vddfdf"
confirmed_at Time.now
confirmation_token nil
factory :superadmin do
after(:create) {|user| user.add_role(:superadmin)}
end
after(:create) do |user|
user.deals << FactoryGirl.create(:deal)
end
factory :user_with_deals do
# used for defining user_deals
transient do
deals_count 5
end
after(:create) do |user, evaluator|
create_list(:deal, evaluator.deals_count, user: user)
end
end
end
end
But now I don't know how to say 'ok of one of these created user_deals in the factory, I'd like one to have number_of_clicks =4, and the other one number_of_clicks=1). so I tried to put this inside the test directly , as below:
describe 'HP deal card features', :type => :feature do
context "As signed-in USER" do
let(:subject) { ApplicationController.new }
before do
#user = FactoryGirl.create(:user, :user_country_name => 'Germany')
#deal1 = FactoryGirl.build( :deal)
#deal2 = FactoryGirl.build( :deal)
#user_deal_with_max_of_clicks = FactoryGirl.build( :user_with_deals,
:user => #user,
:deal => #deal1,
:number_of_clicks => 4
).save(validate: false)
#user_deal_with_clicks_available = FactoryGirl.build( :user_with_deals,
:user => #user,
:deal => #deal2,
number_of_clicks => 1 # still has clicks
).save(validate: false)
it "the right behavior for the buttons telling him how many clicks he has left" do
sign_in #user
visit root_path
# I'll find a way to test here if there is the text "you reached the maximum clicks" for the first card and not for the second
end
after do
visit destroy_user_session_path
end
end
But the test does not work and give me different type of errors according to the small change I try. I am pretty sure I don't manage to really create the 2 objects user_deals , one with number_of_clicks= 4 and the other one number_of_clicks= 1.
EDIT
After requests to post the errors:
If i leave inside the #user_deal_with_max_of_clicks " :user=> #user, and :deal => #deal1", I get
NoMethodError:
undefined method `user=' for #<User:0x0000000b4754e0>
and
NoMethodError:
undefined method `deal=' for #<User:0x0000000b451568>
But I remove them out as follows:
#user_deal_with_max_of_clicks = FactoryGirl.build( :user_with_deals,
:number_of_clicks => 4
).save(validate: false)
then I get this error:
NoMethodError:
undefined method `number_of_clicks=' for #<User:0x0000000b46ce80>
EDIT 2
utilities.rb
include ApplicationHelper
def sign_in(user)
visit new_user_session_path
fill_in I18n.t("formtastic.labels.user.email"), with: user.email
fill_in I18n.t("formtastic.labels.user.password"), with: user.password
click_on I18n.t("devise.sessions.login_page.login")
end
First off, I'd advise you to use let. There is a great guide to write cleaner and better specs.
Secondly, it is vital to set up good factories. They will simplify the testing process enormously:
So starting off, your user factories can be restructured to
FactoryGirl.define do
factory :user do
sequence(:email) { |n| "person#{n}#example.com" }
password "vddfdf"
password_confirmation "vddfdf"
confirmed_at Time.now
confirmation_token nil
trait :superadmin do
role :superadmin
end
trait :with_deals do
after(:create) do |user|
create_list(:deal, 5, user: user)
end
end
end
end
Traits are nice way to selectively adjust factories. So if you want your user to be a superadmin, now just use
create(:user, :superadmin)
Want a superadmin with deals? Easy.
create(:user, :superadmin, :with_deals)
You have not posted your deal factories, but I'm sure you can adapt these tricks to them as well.
Finally, leading to the user_deals. In your current factories, you don't address your number_of_clicks column.
Again, you can easily set this up with traits:
FactoryGirl.define do
factory :user_deal do
association :user
association :deal
trait :few_clicks do
number_of_clicks 1
end
trait :many_clicks do
number_of_clicks 4
end
end
end
Now to the spec itself, with the learnt tricks it's an easy task to set up your desired relations:
describe 'HP deal card features', :type => :feature do
context "As signed-in USER" do
let(:subject) { ApplicationController.new }
let(:user) { create(:user, user_country_name: 'Germany') }
let(:deal1) { create(:deal) }
let(:deal2) { create(:deal) }
before do
create(:user_deal, :few_clicks, user: user, deal: deal1)
create(:user_deal, :many_clicks, user: user, deal: deal2)
end
it "tells the user how many clicks he has left" do
sign_in user
visit root_path
# I'll find a way to test here if there is the text "you reached the maximum clicks" for the first card and not for the second
end
after do
visit destroy_user_session_path
end
end
end

Testing Associations With RSpec in a Chess Application

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

Attribute not accessible via Rspec

Models:
class User < ActiveRecord:Base
has_many :roles
has_many :networks, :through => :roles
end
class Network < ActiveRecord:Base
has_many :roles
has_many :network, :through => :roles
end
class Role < ActiveRecord:Base
attr_accesible :user_id, :network_id, :position
belongs_to :user
belongs_to :network
end
The default for role is "member"
In the console I can type:
> #role = Role.find(1)
> #role.position
=> "member"
But in my Rspec tests, I use FactoryGirl to create a user, network, and role. And I have the test #role.should respond_to(:position) I have also tried just assigning it #role.position = "admin". And no matter what, I get an error like:
Failure/Error: #role.should respond_to(:position)
expected [#<Role id:1, user_id: 1, position: "member", created_at...updated_at...>] to respond to :position
Am I missing something very basic?
EDIT:
factories.rb
FactoryGirl.define do
factory :user do
name "Example User"
sequence(:email) {|n| "email#{n}#program.com"}
end
factory :network do
sequence(:name) {|n| "Example Network #{n}"}
location "Anywhere, USA"
description "Lorem Ipsum"
end
factory :role do
association :user
association :network
position "member"
end
end
network_controller_spec
...
before(:each) do
#user = test_sign_in(FactoryGirl.create(:user)
#network = FactoryGirl.create(:network)
#role = FactoryGirl.create(:role, :user_id => #user.id, :network_id = #network.id)
#I have also tried without using (_id) I have tried not setting the position in the factories as well.
end
it "should respond to position" do
get :show, :id => #network
# This may not be the best or even correct way to find this. But there should only be one, and this method works in the console.
#role = Role.where(:user_id => #user.id, :network_id => #network.id)
#role.should respond_to(:position)
end
Jesse is correct in his comment, hopefully he will come back and write it as an answer, in the meantime, the code should be:
#role = Role.where(:user_id => #user.id, :network_id => #network.id).first
or
#role = Role.find_by_user_id_and_network_id(#user.id, #network.id)
As an aside, it seems a little odd to be testing the role class in the network controller spec (unless this is just an exploratory test to work out why things aren't working as expected).

How to insert rows in a many-to-many relationship

I am having an issue trying to save into an intermediate table. I am new on Rails and I have spent a couple of hours on this but can't make it work, maybe I am doing wrong the whole thing. Any help will be appreciated. =)
The app is a simple book store, where a logged-in user picks books and then create an order.
This error is displayed:
NameError in OrderController#create
uninitialized constant Order::Orderlist
These are my models:
class Book < ActiveRecord::Base
has_many :orderlists
has_many :orders, :through => :orderlists
end
class Order < ActiveRecord::Base
belongs_to :user
has_many :orderlists
has_many :books, :through => :orderlists
end
class OrderList < ActiveRecord::Base
belongs_to :book
belongs_to :order
end
This is my Order controller:
class OrderController < ApplicationController
def add
if session[:user]
book = Book.find(:first, :conditions => ["id = #{params[:id]}"])
if book
session[:list].push(book)
end
redirect_to :controller => "book"
else
redirect_to :controller => "user"
end
end
def create
if session[:user]
#order = Order.new
if #order.save
session[:list].each do |b|
#order.orderlists.create(:book => b) # <-- here is my prob I cant make it work
end
end
end
redirect_to :controller => "book"
end
end
Thnx in advance!
Manuel
Only got time to look at this briefly, I'm afraid, but the first thing I spot is that your has_many relations are called :orderlists. I think that needs to be :order_lists, with an underscore.
This is not directly associated with your question but this query:
book = Book.find(:first, :conditions => ["id = #{params[:id]}"])
...is vulnerable to sql injection. In this case content of params[:id] gets passed to sql without proper escaping. I would suggest changing this line to something like this:
book = Book.find(:first, :conditions => ["id = ?, params[:id]])
Here's explanation: http://wiki.rubyonrails.org/howtos/security/sql_injection
Yes that was one of the problems. Then I could make it work with this line in the 'create' method:
def create
if session[:user]
#order = Order.new
if #order.save
session[:list].each do |b|
OrderList.create(:book => b, :order => #order)
end
end
end
redirect_to :controller => "book"
end
Thanks Chris

Testing an Rspec Controller with associations

I've got two models:
class Solution < ActiveRecord::Base
belongs_to :user
validates_attachment_presence :software
validates_presence_of :price, :language, :title
validates_uniqueness_of :software_file_name, :scope => :user_id
has_attached_file :software
end
class User < ActiveRecord::Base
acts_as_authentic
validates_presence_of :first_name, :last_name, :primary_phone_number
validates_uniqueness_of :primary_phone_number
has_many :solutions
end
with my routes looking like this:
map.resources :user, :has_many => :solutions
Now I'm trying to test my solutions controllers with the following RSpec test:
describe SolutionsController do
before(:each) do
#user = Factory.build(:user)
#solution = Factory.build(:solution, :user => #user)
end
describe "GET index" do
it "should find all of the solutions owned by a user" do
Solution.should_receive(:find_by_user_id).with(#user.id).and_return(#solutions)
get :index, :id => #user.id
end
end
end
However, this gets me the following error:
ActionController::RoutingError in 'SolutionsController GET index should find all of the solutions owned by a user'
No route matches {:id=>nil, :controller=>"solutions", :action=>"index"}
Can anybody point me to how I can test this, since the index should always be called within the scope of a particular user?
Factory#build builds an instance of the class, but doesn't save it, so it doesn't have an id yet.
So, #user.id is nil because #user has not been saved.
Because #user.id is nil, your route isn't activated.
try using Factory#create instead.
before(:each) do
#user = Factory.create(:user)
#solution = Factory.create(:solution, :user => #user)
end
Looks like your other problem is on this line:
get :index, :id => #user.id
You're trying to make a request to the index method, but you've provided the wrong variable name. When testing SolutionsController id implies a solution id, you need to supply the user id. This should work, or at least move you forward:
get :index, :user_id => #user.id

Resources